Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generic constraint: type parameter on the class inherits type parameter on the method?

Tags:

c#

generics

I have a generic class, on which I define a method that should take arguments of another type, but only if the other type implements the type parameter of the class. However, this won't compile:

class GenericClass<TClass>
{
    public void DoSomething<TMethod>(TMethod input) where TClass : TMethod
    {
        // ...
    }
}

I get a compiler error on TClass in the constraint on the method. Is there another way to specify this?

Clarification:
I am under the impression that TMethod : TClass means that TMethod must inherit or implement TClass (depending on whether TClass is a concrete type or an interface). In other, slightly unconventional, notation TMethod > TClass (meaning TMethod is a superset of TClass).

What I want here, is that TMethod should only be a valid type if TClass inherits or implements it, i.e. TClass > TMethod. Will TMethod : TClass do that as well?

(One of the answers states that TMethod : TClass requires TMethod to be assignable from TClass. I'm not sure if that meets my requirements, but if it does, please explain in more detail what being assignable from means, since if it helps me I've probably misunderstood it...)

like image 630
Tomas Aschan Avatar asked Jul 14 '11 15:07

Tomas Aschan


1 Answers

Executive summary:

Chris Hannon's answer is basically correct. However, there are sneaky tricks involving interfaces and extension methods that might get you what you want.

Excessive details:

We do occasionally get asked for this sort of constraint, and there are other languages that have constraints like this. Java and I believe Scala both have ways of saying "this has to be a supertype of that". As others have pointed out, this sort of constraint cannot be represented in either the C# type system or the underlying CLR type system.

The reason why you might want this sort of variance would be for a situation like this:

class ImmutableStack<T>
{
    public static ImmutableStack<T> Empty { get { return empty; } }
    private static ImmutableStack<T> empty = new ImmutableStack<T>();
    private T head;
    private ImmutableStack<T> tail;
    public ImmutableStack<T> Pop()
    {
        if (this == empty) throw new Exception();
        return this.tail;
    }
    public ImmutableStack<N> Push(N value) where T : N // not legal
    {
        var result = new ImmutableStack<N>();
        result.head = value;
        result.tail = this; // also not legal
        return result;
    }
}

And now you can do something like:

Tiger tony = new Tiger();
Elephant dumbo = new Elephant();
ImmutableStack<Tiger> tigers = ImmutableStack<Tiger>.Empty;
tigers = tigers.Push<Tiger>(tony);
ImmutableStack<Mammal> mammals = tigers.Push<Mammal>(dumbo);

And hey, now you can push an elephant onto a stack of tigers and it magically becomes a stack of mammals!

This is not possible in C# as I've shown here for two reasons: first, because there is no such generic constraint, and second, because immutable class types are not covariant. (The assignment to the tail requires covariance.)

However it is possible to do this in C# if you are sneaky. You can get around the covariance problem by representing everything as a covariant interface rather than a class. You can get around the "no such constraint" problem by moving the method in question out of the class proper and making it into an extension method! I give an example of how to do so here:

http://blogs.msdn.com/b/ericlippert/archive/2007/12/06/immutability-in-c-part-three-a-covariant-immutable-stack.aspx

like image 103
Eric Lippert Avatar answered Nov 15 '22 12:11

Eric Lippert