C# 4.0 and variant generic interfaces

The CLI supports variant generic parameters for interfaces as well as delegates, and C# 4.0 has added support for that. In this blog entry I’ll talk a little about variance in generic interfaces, and in a later entry I will talk about variance in delegates.

Covariance allows you to use a more derived type than what’s specified by the generic parameter, whereas contravariance allows you to use a less derived type. Generic interfaces can be covariant, contravariant, or both (though not for the same parameter).

Consider the following code:

class Base
{
}

class Derived : Base
{
}

interface ICovariant<out T> <-- covariant parameter
{
}

class Covariant<T> : ICovariant<T>
{
}

interface IContraVariant<in T> <-- contravariant parameter
{
}

class ContraVariant<T> : IContraVariant<T>
{
}

There’s a base class and a derived class, a covariant interface, an implementation of that interface, a contravariant interface, and its implementation. In C# a covariant parameter is prefixed with out, and a contravariant parameter is prefixed with in. In MSIL the symbols + and - are used for covariance and contravariance. (Note : at the time of writing, Reflector does not recognize or show variant generic parameters, but ildasm does show it correctly).

Here’s some code that shows the variance in action:

ICovariant<Base> baseCov = new Covariant<Base>();
ICovariant<Derived> derivedCov = new Covariant<Derived>();
baseCov = derivedCov; <-- covariance

IContraVariant<Base> baseContra = new ContraVariant<Base>();
IContraVariant<Derived> derivedContra = new ContraVariant<Derived>();
derivedContra = baseContra; <--contravariance

In the first case, we were able to assign a more derived generic instantiation to be assigned to a less derived one. In the second case we did the exact opposite. The Base Class Library has been updated to support covariance and contravariance in various commonly used interfaces. For example, IEnumerable<T> is now a covariant interface – IEnumerable<out T>. This lets us do:

IEnumerable<object> objects = new List<string>();
IEnumerable<Base> baseList = new List<Derived>();

That was something you could not do prior to C# 4.0. Now you won’t have to use LINQ’s Cast<>() as often as you may doing now. There are some rules to follow when using variant generic parameters.

For covariant generic parameters, the generic parameter can only be a return type, it cannot be used as a method argument. It also cannot be used as a generic constraint. (Note : A contravariant delegate can be used as a method parameter that uses the generic parameter as its generic parameter type. I’ll talk about it in my entry on variant delegates)

For contravariant generic parameters, the rules are basically reversed. The parameter type can only be a method argument or a generic type constraint, it cannot be used as a return type. Here’s an interface that has both contravariant and covariant generic parameters.

interface IContraAndCovariant<in T1, out T2>
{
    //T1 Foo(T2 value); <-- won't compile

    T2 Foo(T1 value);
}

Here’s a more realistic example of where covariance comes in very handy:

class Item
{
    public void CheckOut()
    {
        Console.WriteLine("{0} checked out", Code);
    }

    public int Code { get; set; }
}

class Book : Item
{
    public string Title { get; set; }
}

static void CheckOutItem(IEnumerable<Item> source, int code)
{
    try
    {
        source.First(item => item.Code == code).CheckOut();
    }
    catch (InvalidOperationException ex)
    {
        Debug.WriteLine(ex.Message); // invalid code
    }
}

CheckOutItem expects an IEnumerable of Item elements, but because IEnumerable<T> is covariant, I can do this:

List<Book> books = new List<Book>()
{
    new Book(){Code = 99, Title = "C++/CLI in Action"},
    new Book(){Code = 101, Title = "Ajax in Action"},
    new Book(){Code = 105, Title = "ASP.NET Ajax in Action"},
};

CheckOutItem(books, 99);

I have passed an IEnumerable<Book> to a method where an IEnumerable<Item> was expected. And here’s a realistic example of where contravariance can be used:

class ItemComparer : IComparer<Item>
{
    public int Compare(Item x, Item y)
    {
        return x.Code.CompareTo(y.Code);
    }
}

private void ContravarianceTest()
{
    List<Book> books = new List<Book>()
    {
        new Book(){Code = 199, Title = "C++/CLI in Action"},
        new Book(){Code = 501, Title = "Ajax in Action"},
        new Book(){Code = 105, Title = "ASP.NET Ajax in Action"},
    };

    books.Sort(new ItemComparer());
}

I used the List<T>.Sort overload that takes an IComparer<T> argument which in this case would have been an IComparer<Book> but since IComparer<T> is contravariant with regard to T, I could pass my ItemComparer class which is actually an IComparer<Item>.

Funnily, now that it’s available it’s kind of hard to think of how people managed to go without this all these years. It’s more the sort of feature that gets a “woah, we didn’t have this before!” response from people. In a later entry I will discuss variance in delegates.

Advertisements

2 thoughts on “C# 4.0 and variant generic interfaces

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s