开发者

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

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 mus开发者_JAVA百科t 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...)


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


You can't do what you're asking to do in C#. Constraints essentially specify a common assignable point that something must have, and constraining TMethod : TClass will mean that you know that TMethod can at least be treated as TClass.

The problem with constraining TMethod to be "somewhere within the inheritance structure of another type" is that you now have no good way to determine a base type for what you're being given, as far as constraints go. Are you using a specific interface implemented by a base class three levels deep in a completely different type? Are you passing in a base class? Are you passing an unrelated descendant class whose base just happens to fall into the object's inheritance structure? Object is the closest you'll get that matches all possible combinations, and at that point you may as well just forget about using generics and use Object directly because all of the type-safety benefits provided by generics have been thrown out the window.

Reversing that would also defeat the purpose of polymorphism. If a method signature requires class A and my type descends from class A, why should I not be allowed to pass it in as A? If the reverse was true and a method signature potentially expects any base class of B and specifies B instead, you cannot reasonably expect that any functionality of B will be available if you were able to pass in A.


The closest you're going to get, I think, is to pass Object and perform a runtime check for the inheritance relationship.

class GenericClass<TClass>
{
    public void DoSomething(Object input)
    {
        if (input == null) throw new ArgumentNullException("input");
        if (!input.GetType().IsAssignableFrom(typeof(TClass)))
            throw new ArgumentException("Input type must be assignable from " + typeof(TClass).ToString(), "input");
        // ...
    }
}

Note that this will permit inputs of type Object and also (in rare circumstances with transparent remoting proxies) of an interface type that TClass implements. These could be explicitly excluded as well.


You can't do that because TClass is not defined in the method type signature. TClass is defined at the class level, therefor you can't constraint it at a method level.

Try...

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

This is saying, allow this method to be called where TMethod is assignable from TClass.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜