Matt Ruwe
Matt Ruwe

Reputation: 3416

Generic with multiple constraints

I'm trying to call a method with a definition similar to the following (simplified to avoid confusion):

public static void Register<T>(T value) where T : BaseClass, IInterface

This works fine so long as I have a class instance that defines both of those values. The problem occurs when I pass a `BaseClass' into a method and then try to use that instance in the above declaration. For example:

public class MyClass
{
    public MyClass(BaseClass value)
    {
        Register(value);
    }
}

I can pass and instance of a class that implements both BaseClass and IInterface into the constructor, but when I try to use that value in the Register method I get a compilation error stating:

The type 'BaseClass' cannot be used as type parameter 'T' in the generic type or method 'Register(T)'. There is no implicit reference conversion from 'BaseClass' to 'IInterface'.

If I change the type in the constructor like so:

public class MyClass
{
    public MyClass(IInterface value)
    {
        Register(value);
    }
}

I get an error stating:

The type 'IInterface' cannot be used as type parameter 'T' in the generic type or method 'Register(T)'. There is no implicit reference conversion from 'IInterface' to 'BaseClass'.

This seems like a bit of a catch-22. Is there a way that I can define the parameter to indicate that it must implement both BaseClass and IInterface?

Upvotes: 2

Views: 1537

Answers (2)

supercat
supercat

Reputation: 81307

The solution given by Matt is an easy answer for situations where it is not necessary to store the passed-in object in a field or collection. If you need to persist the passed-in object, things get much harder. It's easy for a SomeClass<T> where T meets multiple constraints, to store items of class T and pass them as generics to routines with such constraints, but if a class has a method SomeMethod<TParam>(TParam thing) where TParam:IFoo,BaseBar, it won't have any field of type TParam. It could store thing into a field of type IFoo or BaseBar, but unless BaseBar implements IFoo, or all passed-in instances are going to derive from one particular BaseBar derivative which implements IFoo, there's no way to specify that a field's type will meet both constraints (if every instance does derive from one particular BaseBar derivative which implements IFoo, one could simply use that type as a single constraint, or for that matter not bother with generics at all—just use that as the parameter type).

There are ways of getting around these issues, either using Reflection, an interface pattern I call ISelf<T>, or some tricky nested callback interfaces. In some cases, though, it may be better to provide alternatives to methods that take double-constrained parameters (have the methods take a parameter of one constraint type, cast it to the other type, and accept the lack of compile-time safety).

Upvotes: 1

Matt Ruwe
Matt Ruwe

Reputation: 3416

As I was writing the question I came up with the answer and thought I would post it instead of deleting the question.

I just need to redefine the class:

public class MyClass<T> where T : BaseClass, IInterface
{
    public MyClass(T value)
    {
        Register(value);
    }
}

Upvotes: 7

Related Questions