Reuven Bass
Reuven Bass

Reputation: 680

Forcing type of a generic parameter be an interface instead of a specific class

Given these interface and implementation

interface IService {}
class Service : IService {}

with a generic method

void Register<I>(I service)
{
    var type = typeof(I);
}

How to make the following lines be consistent regarding the generic type?

Register<IService>(new Service())    // type is 'IService'
Register(new Service());             // type is 'Service'

Two options are acceptable:

Upvotes: 2

Views: 1038

Answers (3)

phoog
phoog

Reputation: 43076

As you note, Register(new Service()); of course compiles to Register<Service>(new Service());

Assuming that you can define some logic that would pick one of the concrete type's implemented interfaces as the interface to register (which is a big assumption), you could handle concrete types rather than having the compiler exclude them. Obviously the trivial solution is to require that the type implement only one interface, but that's unlikely to be very useful.

I'm thinking of something along these lines (taking Jon Skeet's suggestion and renaming the type parameter):

void Register<T>(T service) 
{ 
    var type = typeof(T); 
    if (type.IsInterface)
    {
        Register(type);
        return;
    }

    var interfaceType = ChooseTheAppropriateInterface(type);
    Register(interfaceType);
} 

void Register(Type typeToRegister)
{
    //...
}

Type ChooseTheAppropriateInterface(Type concreteType)
{
    var interfaces = concreteType.GetInterfaces();
    //... some logic to pick and return the interface to register
}

All things considered, it's probably simplest and clearest to let the caller specify the desired interface with the Register<IService>(new Service()); call.

EDIT

I agree that Register<IService>(new Service()); is the clearest form. But how can I enforce programmers to not omit the <IService>? Re-sharper, for instance, may suggest that <IService> is redundant.

To answer that question, let's consider the semantics of the call. The call is associating an object (new Service()) with an interface (IService). One way to force programmers to be explicit about the identity of the interface is to make the type a formal parameter. In fact, if you're not calling any of the interface's methods on the object you're registering, you don't even need generics:

void Register(Type serviceType, object service)
{
    // ... some argument validation

    if (!(serviceType.IsAssignableFrom(service.GetType())))
        throw...

    // ... register logic
}

//usage:
void InitializeServices()
{
    Register(typeof(IService), new Service());
}

Even without calling any of the interface's members on the object, however, there's another benefit from generics: compile-time type checking. Is there a way to get compile-time type checking while forcing the developer to specify the type explicitly? Yes: with two type parameters in the method signature, but only one argument, there's no way for compiler to infer both types, so the developer has to supply both. That's more typing, but the code is more explicit.

With that approach, you can also constrain the implementing type to the interface type, to ensure that the developer doesn't call, for example, Register<IValidationService, SecurityService>(new SecurityService());:

interface IServiceBase { } // interface that all service interfaces must implement; might not be needed
interface IService : IServiceBase { }
class Service : IService : { }

void Register<TServiceType, TImplementingObject>(TImplementingObject service)
    where TServiceType : IServiceBase  // superfluous if there's no IServiceBase, of course
    where TImplementingObject : TServiceType
{
    // ... implementation
}

//usage:
void InitializeServices()
{
    Register<IService, Service>(new Service());
}

or even

class NewImprovedService : Service : { }

void InitializeServices()
{
    Register<IService, Service>(new NewImprovedService());
}

This gets us back to the point of some possibly-redundant type indications, and the call is even more verbose than what you started with, but it does prevent the developer from inadvertently registering the wrong service type.

We are still left with the original problem, however, as there's nothing stopping the developer from calling

Register<Service, NewImprovedService>(new NewImprovedService());

That won't fail until a runtime check for typeof(TServiceType).IsInterface.

Upvotes: 1

Grant Thomas
Grant Thomas

Reputation: 45058

You mean to constrain the type of I to IService, right?

Use the where clause to constrain generic types:

void Register<TServiceType>(TServiceType service)
    where TServiceType : IService

If what you're asking is to restrict the parameter to be any interface, then, as stated by Jon (yet in an attempt to make my answer less redundant), this is illegal.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1504062

You can't - there's no way of constraining a type parameter to be an interface.

You could perform an execution time check, of course, and throw an exception if I isn't an interface. (As an aside, naming conventions would suggest this should be T, not I.)

Upvotes: 7

Related Questions