views:

812

answers:

3

Why, with a generic constraint on type parameter T of class P of "must inherit from A", does the first call succeed but the second call fail with the type conversion error detailed in the comment:

abstract class A { }

static class S
{
    public static void DoFirst(A argument) { }
    public static void DoSecond(ICollection<A> argument) { }
}

static class P<T>
    where T : A, new()
{
    static void Do()
    {
     S.DoFirst(new T());             // this call is OK

     S.DoSecond(new List<T>());      // this call won't compile with:

     /* cannot convert from 'System.Collections.Generic.List<T>'
        to 'System.Collections.Generic.ICollection<A>' */
    }
}

Shouldn't the generic constraint ensure that List<T> is indeed ICollection<A>?

+7  A: 

This is an example of C#'s lack of covariance on generic types (C# does support array covariance). C# 4 will add this feature on interface types and also will update several BCL interface types to support it as well.

Please see C# 4.0: Covariance and Contravariance:

In this article I’ll try to cover one of the C# 4.0 innovations. One of the new features is covariance and contravariance on type parameters that is now supported by generic delegates and generic interfaces. First let’s see what does these words mean :)

Andrew Hare
This will not change in C# 4.0. `IList<T>` and `ICollection<T>` are _not covariant_. So even though C# 4.0 will add support for type covariance, this particular case will keep not working.
Pavel Minaev
Pavel, you seem to be confused here. The types in play are NOT IList<T> and ICollection<T>...they are List<T> (the concrete type, not the interface) and ICollection<T>. A List<T> is ABSOLUTELY an ICollection<T>, and that does make them covariant. You posted your comment in THREE posts, and apparently downvoted two of them...based on the wrong type...its not IList<T>, its List<T>!
jrista
A: 

The constraint has no effect on the problem; the issue is that you're passing a List in a parameter that requires ICollection--C# doesn't support covariance so you need to explicitly cast the list to an ICollection:

S.DoSecond((ICollection<A>) new List<T>());      // this call will be happy
STW
The problem isn't `IList`/`ICollection` mismatch, the problem is type argument mismatch.
Pavel Minaev
A: 

You have strongly typed the parameter for DoSecond as type ICollection<A>. Despite the fact that T is of type A, at compile time there is no implicit cast between List<T> and ICollection<A>. You will either need to create the list and cast it to ICollection<A> when you call DoSecond, or make DoSecond a generic method itself.

NOTE: This type of implicit cast should be supported in C# 4.0, which will provide much improved co/contravariance over what C# 3.0 offers.

jrista
Er yes, there is an implicit upcast between T and A. There is no upcast between `IList<T>` and `IList<A>`. Also, covariance support in C# 4.0 won't help here, because neither `IList<T>` nor `ICollection<T>` are covariant in and of themselves.
Pavel Minaev
I edited my post to clarify my meaning. The types used were List<T> and ICollection<A>. List<T> is indeed an ICollection<T>, and since T is of A, ICollection<T> should be covariant with ICollection<A> in C# 4.0. I apologize for the original lack of clarity...hopefully my edit makes it more clear.
jrista