Saturday, March 12, 2011

Covariance and Contravariance FAQ

In this post I’ll try to answer the most common questions I find on forums and in documentation feedback about C# covariance and contravariance. It’s a big topic for a single blog post, so expect to see a lot of “more information” links.

Special thanks to Eric Lippert and Chris Burrows for reviewing and providing helpful comments.

What are covariance and contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments.Covariance preserves assignment compatibility and contravariance reverses it.
The following code demonstrates the difference between assignment compatibility, covariance, and contravariance.

// Assignment compatibility. 
string str = "test";

// An object of a more derived type is assigned to an object of a less derived type. 
object obj = str;

// Covariance. 
IEnumerable<string> strings = new List<string>();

// An object that is instantiated with a more derived type argument 
// is assigned to an object instantiated with a less derived type argument. 
// Assignment compatibility is preserved. 
IEnumerable<object> objects = strings;

// Contravariance.           
// Assume that I have this method: 
// static void SetObject(object o) { }

Action<object> actObject = SetObject;

// An object that is instantiated with a less derived type argument 
// is assigned to an object instantiated with a more derived type argument. 
// Assignment compatibility is reversed.

Action<string> actString = actObject;

In C#, variance is supported in the following scenarios:

Covariance in arrays (since C# 1.0)
Covariance and contravariance in delegates, also known as “method group variance” (since C# 2.0)
Variance for generic type parameters in interfaces and delegates (since C# 4.0)

What is array covariance?

Arrays are covariant since C# 1.0. You can always do the following:

object[] obj = new String[10];

In the above code, I assigned an array of strings to an array of objects. So I used a more derived type than that originally specified, which is covariance. 
Covariance in arrays is considered “not safe,” because you can also do this:

obj[0] = 5;

This code compiles, but it throws an exception at run time because obj is in fact an array of strings and cannot contain integers.

What is delegate, or method group, variance?

This feature was added in C# 2.0. When you instantiate a delegate, you can assign it a method that has a more derived return type than that specified in the delegate (covariance). You can also assign a method that has parameter types less derived than those in the delegate (contravariance).
Here’s a quick code example illustrating the feature and some of its limitations.

static object GetObject() { return null; }
static void SetObject(object obj) { }
static string GetString() { return ""; }
static void SetString(string str) { }
static void Main()
{
    // Covariance. A delegate specifies a return type as object,
    // but I can assign a method that returns a string.
    Func<object> del = GetString;
    // Contravariance. A delegate specifies a parameter type as string,
    // but I can assign a method that takes an object.
    Action<string> del2 = SetObject;
    // But implicit conversion between generic delegates is not supported until C# 4.0.
    Func<string> del3 = GetString;
    Func<object> del4 = del3; // Compiler error here until C# 4.0.
}

By the way, this feature works for all delegates, both generic and non-generic, not just for Func and Action delegates.

For more information and examples, see Covariance and Contravariance in Delegates on MSDN and Eric Lippert’s post Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance.
What is variance for generic type parameters?

This is a new feature in C# 4.0. Now, when creating a generic interface, you can specify whether there is an implicit conversion between interface instances that have different type arguments. For example, you can use an interface instance that has methods with more derived return types than originally specified (covariance) or that has methods with less derived parameter types (contravariance). The same rules are applied to generic delegates.

While you can create variant interfaces and delegates yourself, this is not the main purpose for this feature. What is more important is that a set of interfaces and delegates in .NET Framework 4 have been updated to become variant.

Here’s the list of updated interfaces:
IEnumerable<T> (T is covariant)
IEnumerator<T> (T is covariant)
IQueryable<T> (T is covariant)
IGrouping<TKey, TElement> (TKey and TElement are covariant)
IComparer<T> (T is contravariant)
IEqualityComparer<T> (T is contravariant)
IComparable<T> (T is contravariant)