Sunday, June 24, 2007

Covariance and Generics

Have you ever thought about why the following code works? Or are you astounded why I ask this question?

class Base{}
class Derived : Base{}

void Foo(Base[] b)
{
    foreach(Base b in b)
        Console.WriteLine(b);
}

Foo(new Derived[]{new Derived(), new Derived()});

Arrays are covariant in C#. This means that you can assign an array of derived types to an array of base types as shown above. Having a strong C++ background, I always wondered why this is possible, because typeof(object[])!= typeof(A[]) is definitely true. And what even makes me wonder more is how often I use the covariance of arrays today!

I noticed that I unintentionally adopted it, when I first tried out .NET Generics about a year

ago and asked myself whether I can pass an IEnumerable<Derived> as an IEnumerable<Base>. Well, I couldn't and was a little bit disappointed. Of course I understand that generics are not covariant and shouldn't be, but still it was annoying to copy the content of one collection to another:

IEnumerable<Base> derived = ...

ICollection<Base> copy = new List<Base>();

foreach(Derived d in derived) 
   copy.Add(d);

So I decided to provide an adapter implementation to avoid the copying. The new C# 2.0 iterator feature made this task very easy. Basically it generates the IEnumerable<T> implementation for you and eliminates the need for the for copying the collection:

public static class Adapt
{
    public static IEnumerable<Base>
       Covariant(IEnumerable<Derived> derived) where Derived : Base
    {
        foreach (Derived b in derived)
            yield return b;
    }
}

This covariant adapter and some more for adapting non generic collections of the System.Collections namespace to their generic counterpart and vice versa are available through the NSTL project, a port and adaption of the C++ STL for .NET.

No comments: