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.
Tks for your detail description. I has spent alot of time to find the poster like this. Many Tks
Thanks, you explained simply but very well detailed.