Home > .NET > C# 4.0: Covariance and Contravariance

C# 4.0: Covariance and Contravariance

April 13th, 2009

Deprecated: Function split() is deprecated in /home/tlk/public_html/.com/t-l-k/blog/wp-content/plugins/google-analytics-for-wordpress/googleanalytics.php on line 481

Deprecated: Function split() is deprecated in /home/tlk/public_html/.com/t-l-k/blog/wp-content/plugins/google-analytics-for-wordpress/googleanalytics.php on line 481

In this article I’ll try to cover one of the C# 4.0 innovations. One of the new features is covariance and contravariance on type parameters that is now supported by generic delegates and generic interfaces. First let’s see what does these words mean :)

Generally if we have some entity (interface or delegate) that is generic on type T – some Entity<T>, and two concrete entities Entity<A> and Entity<B> where B inherits from A, then there are no inheritance relationships between Entity<A> and Entity<B>. Covariance (and contravariance) adds such relationships:

If there is some CoEntity<T> that is covariant on T and B inherits from A, then CoEntity<B> inherits from CoEntity<A>.

And if there is some ContraEntity<T> that is contravariant on T and B inherits from A, then (vice versa) ContraEntity<A> inherits from ContraEntity<B>.

Co- or contravariance adds some restrictions to corresponding interface or delegate. Covariance is supported in only some cases, and in some other cases is supported contravariance. Let’s check out what are these restrictions.


Restrictions

First restriction is that covariance and contravariance issues are available only to delegates and interfaces :).

The second one is that generic type used for co- or contravariance should be a reference type. However generic type is not restricted to be a reference type only. Value type can be used too, but there will be no inheritance relationships for it.

interface ICovariant<out T> { }

interface IInterface { }

struct SomeStruct : IInterface { } // value type, inherits IInterface

class CovariantS : ICovariant<SomeStruct> { }

static void Main(string[] args)
{
    ICovariant<SomeStruct> covariantS = new CovariantS();
    ICovariant<IInterface> covariantI = covariantS; // compiler error
}

And the third (and most important one) is following: Type that is to be used for covariance can be used only as type for return values in corresponding interface of delegate. And type that is to be used for contravariance can be used only as type for input parameters in corresponding interface of delegate. (Here and in whole article I mean that using type for setter method is the same as using it as input parameter, and using it for getter method is the same as using it as return value.) Also both covariant and contravariant types cannot be used as types for ref (or out) parameters.

delegate T /* allowed */ CovariantProcessor<out T>(
    T value /* not allowed */, ref T reference /* not allowed */);

delegate T /* not allowed */ ContravariantProcessor<in T>(
    T value /* allowed */, ref T reference /* not allowed */);

interface ICovariant<out T>
{
    T Generate(); // allowed
    void Use(T value); // not allowed
    void Change(ref T reference); // not allowed

    T Value
    {
        get; // allowed
        set; // not allowed
    }
}

interface IContravariant<in T>
{
    T Generate(); // not allowed
    void Use(T value); // allowed
    void Change(ref T reference); // not allowed

    T Value
    {
        get; // not allowed
        set; // allowed
    }
}

Without such restrictions we will reach a lot of collisions. It is better to consider an example. Let’s use a Mammal, a Dog and a Cat as our classes.

class Mammal { }

class Dog : Mammal { }

class Cat : Mammal { }

Now let’s see what collisions will we reach with covariance that allows input parameters:

interface ICovariantWrapper<out T>
{
    T Value
    {
        get;
        set; // not really allowed
    }
}

class Wrapper<T> : ICovariantWrapper<T>
{
    T Value { get; set; }
}

static void Main(string[] args)
{
    ICovariantWrapper<Dog> wrappedDog = new Wrapper<Dog>();

    // making wrapped mammal the same as wrapped dog is
    ICovariantWrapper<Mammal> wrappedMammal = wrappedDog;

    // putting a cat into the wrapped dog
    wrappedMammal.Value = new Cat();

    Dog dog = wrappedDog.Value; // there's really a cat!
}

The same collision will be in contravariant case. The only difference is that we will first create IContravariantWrapper<Mammal> and then cast it to IContravariantWrapper<Dog>. After that a Cat can be assigned to where a Dog should be.

Honestly, the third restriction is more compicated. But we will talk about it at the end of article, when we will better understand what is going on here :)

Knowing the very theory is not enough :) Let’s check out examples where co- and contravariance are good to be used.


Covariance Example

We will use interface ICreator<T> that is able to create instances of T from different sources. Also we will use two classes: Entity and it’s subclass SerializableEntity.

public interface ICreator<T>
{
    T CreateDefault();

    T CreateFromXDocument(XDocument xDocument);

    T CreateFromStream(Stream stream);
}

public class Entity
{
    /* ... */
}

public class SerializableEntity : Entity
{
    /* ... */
}

Now imagine we have class SerializableEntityCreator that implements interface ICreator for SerializableEntity. And also imagine we have class EntityManager that needs an instance of ICreator<Entity> to work properly.

public class SerializableEntityCreator : ICreator<SerializableEntity>
{
    /* ... */
}

public class EntityManager
{
    public EntityManager(ICreator<Entity> entityCreator)
    {
        /* ... */
    }
}

Let’s now see what we have. On one hand we have SerializableEntityCreator that is able to create instances of SerializableEntity. It also can create instances of Entity because each SerializableEntity is an Entity too. On other hand we have EntityManager that needs something that can create instances of Entity. It seems like SerializableEntityCreator will match EntityManager’s needs, but without covariance it isn’t true. And following code will not compile:

ICreator<SerializableEntity> entityCreator
    = new SerializableEntityCreator();
EntityManager manager = new EntityManager(entityCreator); // error

To resolve this issue in C# 2.0/3.0 we need to implement both ICreator<Entity> and ICreator<SerializableEntity> in SerializableEntityCreator class or even create whole new class that will wrap class SerializableEntityCreator to implement ICreator<Entity> in case when modifications to SerializanleEntityCreator are not allowed. But in C# 4.0 this can be fixed just declaring interface covariant – that means adding to declaration out keyword before generic param T.

public interface ICreator<out T>
{
    /* ... */
}

Now ICreator<T> is covariant by T. That means SerializableEntityCreator is not only ICreator<SerilizableEntity>, but it is an ICreator<Entity> too. And the following code will work:

ICreator<SerializableEntity> entityCreator
    = new SerializableEntityCreator();
EntityManager manager = new EntityManager(entityCreator); // success

Contravariance Example

Imagine we have: generic delegate DeblockingProcessor<T> that is to process any kind of images, some generic interface IImageSequence<T> that uses this delegate and a class BitmapSequence that implements our interface for a concrete image type – Bitmap:

public delegate void DeblockingProcessor<T>(T image, int level);

public interface IImageSequence<T>
{
    void DeblockAll(DeblockingProcessor<T> deblocker);
}

public class BitmapSequence : IImageSequence<Bitmap>
{
    public void DeblockAll(DeblockingProcessor<Bitmap> deblocker)
    {
        /* ... */
    }
}

Now if we have some deblocking processor that works for Image type, we might want to use it with BitmapSequence (Image is a superclass for Bitmap). Unfortunately in C# 3.0 following code would not compile:

BitmapSequence bitmaps = new BitmapSequence();

DeblockingProcessor<Image> imageDeblocker = // getting some deblocker
    ProcessorsFactory.Instance.GetDeblockingProcessor();

bitmaps.DeblockAll(imageDeblocker); // compiler error

In C# 3.0/2.0 we cannot directly pass DeblockingProcessor<Image> as a param to a method that needs DeblockingProcessor<Bitmap> (because one cannot be directly casted to other). Solution is to wrap existent delegate with an anonymous one that has needed signature (and is of type DeblockingProcessor<Bitmap>):

bitmaps.DeblockAll(
    (Bitmap image, int level) => imageDeblocker.Invoke(image, level));

In C# 4.0 this may be easily fixed making delegate contravariant on T by adding in keyword to it’s declaration:

public delegate void DeblockingProcessor<in T>(T image, int level);

After this change DeblockingProcessor<Image> can be casted to DeblockingProcessor<Bitmap> and the following code will work:

bitmaps.DeblockAll(imageDeblocker); // success

As we can see from this example, in C# 2.0/3.0 we are able to use anonymous delegates instead of contravariance. But if we’ll have interface passed as a param instead of delegate (some IDeblockingProcessor<T>, that is needed to perform series of calls like Prepare(T image), Start(), Clean(), etc.), then contravariance is very useful, because we have no anonymous interfaces in .NET.


Covariance and Contravariance in C# 2.0/3.0

It should be noted that C# 2.0/3.0 already supports some way of covariance and contravariance in delegates. It is when we’re creating new delegate from a method (in many cases we do not code it – it’s performed automatically). Delegate constructor can take as a param not only a method with same signature, but with signature that is co- or contravariative to delegate’s. And it works not only for delegates with generic types, but for all delegates (the only exception is for ref parameters).

It is easy enough to understand this with examples. We will use XNode and XElement, where XElement is a subclass of XNode (between them is also XContainer, but it is not so often used). Let’s define following delegates and methods.

public delegate void XElementCacher(XElement xElement);
public delegate XNode XNodeGenerator();
public delegate XNode XObjectCopier(XElement xElement);

public static void CacheXNode(XNode xNode)
{
    /* ... */
}

public static XElement GenerateXElement()
{
    /* ... */
}

public static XElement CopyXNode2XElement(XNode xNode)
{
    /* ... */
}

As we can see, none of the methods’ signature corresponds to any of delegates’ signatures. However, following assignments are all correct:

// contravariance
XElementCacher xElementCacher = CacheXNode;

// covariance
XNodeGenerator xNodeGenerator = GenerateXElement;

// mixed - tricky one, heh :)
XObjectCopier xElement2NodeCopier = CopyXNode2XElement;

// and here is what really happens
xElementCacher = new XElementCacher(CacheXNode);
xNodeGenerator = new XNodeGenerator(GenerateXElement);
xObjectCopier = new XObjectCopier(CopyXNode2XElement);

As I said, this works for delegates with generic types too. So if we are creating delegates directly from methods (not using delegates created somewhere else, as I’ve done in my example), then we do not need to extend delegate declaration with in/out modifiers – covariance and contravariance in such case are already supported.


More About Third Restriction

Most of the sources are talking about this restriction in simplified manner: T can be used only for return values in covariant types, and T can be used only for input parameters in contravariant types. I’ve already described why. But now I’ll tell you more: in covariant types under some circustances T can be used for input parameters! And in contravariant types T can be used for return values!

Here are the full rules on how generic type T can be used in covariant interface/delegate:

  • all ref/out parameters should be independent on T;
  • all input parameters should be contravariant on T (or independent on T);
  • all return values should be covariant on T (or independent on T).

And these are for contravariant (on T) interface/delegate:

  • all ref/out parameters should be independent on T;
  • all input parameters should be covariant on T (or independent on T);
  • all return values should be contravariant on T (or independent on T).

As T is always covariant on itself, the usual version of the third restriction is just a particular case of full rules.

Following is simple example that shows how it works. Let’s declare two types that are co- and contravariant on T.

interface ICovariant<out T> { }

interface IContravariant<in T> { }

Using power of full rules :) following delegates will work perfectly as covariant and contravariant:

delegate ICovariant<T> CovariantHandler<out T>(IContravariant<T> x);

delegate IContravariant<T> ContravariantHandler<in T>(ICovariant<T> x);

Conclusion

I must appreciate that development of sophisticated program architecture is going to be a bit easier having covariant generic collections (not only ConvertAll method). Sure it is an opened question how to put elements into such collection correctly, because generic type cannot be used for input if we wonna to have it for covariance. However, interfaces have a large range of usage, and covariance is a good new feature for them.

But, talking about delegates, new abilities, as for me, is not a big breakthrough. That is because in some cases covariance and contravariance are already supported by previous versions of C#, and in other cases we can use anonymous delegates to emulate them.

Also I recommend to investigate hidden problems that may probably occur after enabling co- or contravariance for a widely used type. As a start point Eric Lippert’s article Breaking Changes can be used.

P.S. Covariance and contravariance are supported by IL since very 2.0 version of .NET Framework. We can think of this in two ways - great abilities of IL or limitations of C# :)

P.P.S. Thanks to Andrew Bezzub for his comments on this article during it’s creation :)

.NET , , ,

  1. Max
    April 14th, 2009 at 12:21 | #1

    Good job

  2. April 14th, 2009 at 18:52 | #2

    Thanks, Max :) I’ve tried to cover this article as deep as possible.

  3. XJ
    December 18th, 2009 at 02:52 | #3

    Good article.

  4. January 11th, 2010 at 02:40 | #4

    Thanks for very good examples. I will use some of these on my next session about .NET Framework 4.0 and languages.

  1. No trackbacks yet.