Skip to content

Generic Variance in C# 4.0

October 28, 2008

Although I’m not cool enough to actually go to PDC, I’ve been watching some of the things that have been announced. One of the things I’m most excited about is co- and contra-variance in generics, which is something that the CLR has lacked since generics were first introduced in 2.0. (Note: some of the examples on here are pulled from the excellent description of new features release by Microsoft.)

In versions of C# prior to 4.0, generics were invariant. For example, consider this simple type definition:

public class Foo<T>
{
  //...
}

Since the generic type parameter T was not constrained, the compiler understands that T should be treated as type object. That means that since a string is an object, a Foo<string> is functionally equivalent to a Foo<object>. However, because of generic invariance, ian instance of Foo<string> cannot be assigned to a variable of type Foo<object>.

C# 4.0 introduces the ability to declare covariant and contravariant generics. For example:

public class Foo<out T>
{
  //...
}

This class is covariant in T, meaning that if you create a Foo<string>, you can use it effectively as a Foo<object>, since a string is a subclass of object. The example given is the new IEnumerable<T> interface that comes with the BCL in C# 4.0:

public interface IEnumerable<out T> : IEnumerable
{
  IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
  bool MoveNext();
  T Current { get; }
}

Since these interfaces are covariant in T, an IEnumerable<string> can be used as an IEnumerable<object>. The same is true for List<T>, so you’ll be able to do this, which was previously impossible:

IList<string> strings = new List<string>();
IList<object> objects = strings;

Note, however, that you can only declare that your type is covariant for generic type parameters that appear in output positions — basically, return values.

Like the out keyword, you can also use the in keyword:

public interface IComparer<in T>
{
  int Compare(T left, T right);
}

This interface is contravariant in T, meaning that if you have an IComparer<object>, you can use it as though it was a IComparer<string>. Contravariance carries a similar restriction to covariance, in that contravariant type parameters can only be used in input positions (arguments) on the type.

I didn’t quite understand variance until I considered the changes to the Func delegate:

public delegate TResult Func<in TArg, out TResult>(TArg arg);

Func is contravariant in TArg, and covariant in TResult. For example, consider this (less-than-useful and slightly-contrived) method:

public string Convert(object obj)
{
  return obj.ToString();
}

With the new variance rules, I can do this:

Func func1 = Convert;
Func func2 = Convert;

Since the delegate is contravariant in TArg, I can pass in a string, since a string is an object. Since the delegate is covariant in TResult, I can use it in a situation where an object is expected as a result from the function.

This might seem a little overwhelming if you’re not a language geek like myself, but it basically means that things that seemed like they should work in previous versions (like the List<T> example above), now will just work. Eric Lippert also has a great series of posts about the topic.

If you’d like to tinker with C# 4.0 (and Visual Studio 2010), Microsoft has published a VPC image. Kudos to the C# and CLR teams for getting this stuff to work!

About these ads

From → miscellaneous

7 Comments
  1. If you say that this works:

    IList strings = new List();
    IList objects = strings;

    how is this goingo to work then:

    objects.Add(new object());

    ?
    this should work for IList but not for IList !

    so I don’t think that IList is going to have an “out” type parameter, rather the GetEnumerable method of IList might become IEnumerable – though I haven’t read the spec yet and I don’t know if you can actually add the out or in keyword to certain methods/properties to the class parameter.
    (BTW: That’s the way Java Generics do it: out T and in T is like “? extends T” and “? super T”)

    Other than that, great article and thanks for the link!

    /Sebastian

  2. Damn, the comment system removed my brackets – replace {} in the below code with the corresponding brackets

    If you say that this works:

    IList{string} strings = new List{string}();
    IList{object} objects = strings;

    how is this goingo to work then:

    objects.Add(new object());

    ?
    this should work for IList{object} but not for IList{string} !

    so I don’t think that IList is going to have an “out” type parameter, rather the GetEnumerable method of IList{T} might become IEnumerable{out T} – though I haven’t read the spec yet and I don’t know if you can actually add the out or in keyword to certain methods/properties to the class parameter.
    (BTW: That’s the way Java Generics do it: out T and in T is like “? extends T” and “? super T”)

    Other than that, great article and thanks for the link!

    /Sebastian

  3. @Sebastian: That’s a good point. The list example comes directly from the official explanation of the new features, but I haven’t tried it myself in the VS2010 CTP. I’d be interested to know…

  4. >>IList{string} strings = new List{string}();
    >>IList{object} objects = strings;
    In IList, T appears in both output and input positions (i.e. add, T []), therefore IList can’t be covariant on T. In Java, this would be possible with wildcards.

  5. Oidon permalink

    As far as I understand, variance support is only being added for generic interfaces and delegates. (Actually, version 2.0 of the CLI has always supported this; C# is just catching up now.) Thus, your “class Foo” should not compile.

Trackbacks & Pingbacks

  1. Reflective Perspective - Chris Alcock » The Morning Brew #212
  2. InterKnowlogy Blogs » Blog Archive » The Evolution of Generics in C# 4.0

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: