01 Dec 2010

As of C# 4.0, generics support covariance and contravariance. I won’t talke about contravariance in the post, however.

To sum it up quickly, covariance enables implicit conversion of a generic collection of type T to the same generic collection of a type that derives from T. In short:

IEnumerable<Object> list = new List<String>().AsEnumerable();

This means that as of now, any method that accepts something like IEnumerable<Object> can accept an IEnumerable<String> as well. This changes the overload resolution mechanism:

class A { }
class B : A { }

class T
{
    internal void Foo(IEnumerable<B> sequence)
    {
        Console.WriteLine("In T.Foo.B");
    }
}

class U : T
{
    internal void Foo(IEnumerable<A> sequence)
    {
        Console.WriteLine("In U.Foo.A");
    }
}

In the following code, how does the method resolution changes?

U u = new U();

var l = new List<A>();
var m = new List<B>();

u.Foo(l);
u.Foo(m);

If ran in Visual Studio as a .NET 3.5 application, here is the result:

Overload35

If ran in Visual Studio as a .NET 4.0 application, here is the result:

Overload40

Prior to C# 4.0, U.Foo was not a candidate for a call using a generic collection of B. However, with covariance, it is, hence the different result. So, this is no breaking change, but the behavior of an application might be affected.