Understanding Co and Contra Variance in .NET 4

Of all the features that are coming in .NET 4, perhaps the one that is the most exciting to me is Covariance and Contravariance.  So why does this excite me more then EF and other new features coming; cause for those of us who leverage generics and polymorphism the updates to variance are a god send. Consider the following example:

This is the abstract class we are going to demonstrate against:

public abstract class Vehicle
{
     public abstract int NumberOfWheels { get; }
     public abstract void Turn(string direction);
     public string Name { get; set; }

     public virtual void Drive()
     {
          Console.WriteLine("Driving Vehicle");
     }
}

We create the following concrete classes defined using the Vehicle abstract class:

public class Car : Vehicle
{
     public override int NumberOfWheels
     {
          get { return 4; }
     }

     public override void Turn(string direction)
     {
          Console.WriteLine("Turning " + direction + " on " + NumberOfWheels + " wheels");
     }

     public override void Drive()
     {
          Console.WriteLine("Driving the Car");
     }
}

public class Truck : Vehicle
{
     public override int NumberOfWheels
     {
          get { return 6; }
     }

     public override void Turn(string direction)
     {
          Console.WriteLine("Truck is Turning " + direction + " on " + NumberOfWheels + " wheels");
     }

     public override void Drive()
     {
          Console.WriteLine("Driving the Truck");
     }
}

Now, we will define an extension method to operate on both of these types, we can do this by operating on the abstract Vehicle type:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

public static void DriveAll(this IEnumerable listing)
{
     foreach (Vehicle v in listing)
     {
           v.Drive();
     }
}

So given all this, you might expect this bit of code to compile in C#:

static void Main(string[] args)
{
     List carList = new List()
                                    {
                                        { new Car() { Name = "Nissan Sentra" } },
                                        { new Car() { Name = "Volkswagon Passat" } },
                                        { new Car() { Name = "Toyota Camry" }}
                                    };

     carList.DriveAll();
     Console.Read();
}

You would be wrong, this code does not compile because .NET 3.5 does not support covariance and contravariance.  This means that .NET will ONLY analyze the outer type in this case, so IEnumerable and not look at the inner type which would allow it to know that since Car inherits from Vehicle this is ok.  To get around this we employ a interface trick to allow us to control the inner type and “trick” .NET.  To do this, we first need an interface:

public interface IVehicle
{
     int NumberOfWheels { get; }
     void Turn(string direction);
     void Drive();
}

Next we update the Vehicle definition to implement the IVehicle interface like such:

public abstract class Vehicle : IVehicle
{
     // ...
}

By doing this we are able to constrain the inner type and control it.  The key thing to understand here is that the extension method is attempting to find a similar signature to attach to, with thus approach we can give it that, while still keeping the flexibility of working with all types inheriting Vehicle.  This is our updated signature:

public static void DriveAll(this IEnumerable listing) where T : IVehicle
{
     foreach (T v in listing)
     {
          v.Drive();
     }
}

To understand what is happening here, we are effectively taking control over the inner type through generic constraints.  By doing this we are able to provide the signature that .NET 3.5 will need need to provide the extension.  In addition, since we are using the constraint we maintain the flexibility and requirements for the internal type.

However, in .NET 4 where we have covariance and contravariance, we don’t need the interface approach through a generic method.  The inspection of the extension method will encompass an analysis of the internal type to determine the inheritance.  This is already being done in 3.5 on the outer type (try using IEnumerable for List), in .NET 4 we are now having it done on the internal type as well.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Advertisements

One thought on “Understanding Co and Contra Variance in .NET 4

  1. The code you wrote below “So given all this, you might expect this bit of code to compile in C#:” does not make sense to me.

    You used the IVehicle interface before you created it.

    Like

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