Variance - Covariance and Contravariance in C# 4 and .Net 4.0



Download

What Exactly Does Variance Mean?

So according to Wikipedia and various other sources variance is all about the ordering or hierarchy of types and how they can be treated in various different scenarios.
Simply:

  • Covariance - allows you to treat something like it's base class. Such as treat a FordCar like a Car.
  • Contravariance - allows you to treat a base class or supertype like it's subtype.
  • Invariance - you can't do either of the above.

Covariance Example

Imagine we have a Car type and then we have a FordCar type which inherits from it. If we had a simple method called PerformHandbrakeTurn(Car car) which performed a simple operation on a Car we'd expect to be able to pass a FordCar because it's a Car and everything work fine. Happily it does, this is a form of Co-Variance.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Variance.Net35
{
    class Program
    {
        abstract class Car {public string Name { get; set; }}
        class FordCar : Car{}
        
        static void Main(string[] args)
        {
            var fordCar = new FordCar(){Name = "Dans Ford Car"};
            PerformHandbrakeTurn(fordCar);

            Console.ReadLine();
        }

        static void PerformHandbrakeTurn(Car car)
        {
            Console.WriteLine("Performing Handbrake Turn on " + car.Name);
        }
    }
}

Co-variance is where class like FordCar gets converted or can be treated like a more abstract type like Car. So everything works smoothly only it doesn't in some circumstances.

Broken Covariance in Arrays

Say we for some reason wanted to create the method GiveCarsAParkingTicket(Car[] carsWhoDeserveATicket), then say we pass it an array of FordCar's. Internally the method swaps element [0] Dans Ford Car, for Dans Vauxhall Car. A VauxhallCar is still a Car so everything should be fine right?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Variance.Net35
{
    class Program
    {
        abstract class Car {public string Name { get; set; }}
        class FordCar : Car{}
        class VauxhallCar : Car{}
        
        static void Main(string[] args)
        {
            var fordCarArray = new[] {new FordCar {Name = "Dans Ford Car"}, new FordCar {Name = "Beccis Ford Car"}};
            GiveCarsAParkingTicket(fordCarArray);

            Console.ReadLine();
        }

        static void GiveCarsAParkingTicket(Car[] carsWhoDeserveATicket)
        {
            carsWhoDeserveATicket[0] = new VauxhallCar{Name = "Dans Vauxhall Car"};
            foreach (var car in carsWhoDeserveATicket)
            {
                Console.WriteLine("Ha " + car.Name + " you're getting a Parking Ticket");
            }
            
        }
        static void PerformHandbrakeTurn(Car car)
        {
            Console.WriteLine("Performing Handbrake Turn on " + car.Name);
        }
    }
}

Everything compiles fine, but at runtime we get an ArrayTypeMismatchException, telling us Attempted to access an element as a type incompatible with the array. But it's an array of Car[], and a VauxhallCar is a car? Sorry no such like for arrays. Eric Lippert (who was the main source for this post), calls this broken co-variance.

Covariance in C# 3 (.Net 3.5) with IEnumerable<Car>

So enough with parking tickets, let's instead give an IEnumerable<Car> a wash with a method called GiveCarsAWash(IEnumerable<Car> carsToWash). We can simply define an IEnumerable<FordCar> assign our FordCar[] to it, and then pass our IEnumerable<FordCar> to GiveCarsAWash and we should get some clean cars.

Unfortunately this time compiler says no.

Here's the full code listing

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Variance.Net35
{
    class Program
    {
        abstract class Car {public string Name { get; set; }}
        class FordCar : Car{}
        class VauxhallCar : Car{}
        
        static void Main(string[] args)
        {
            var fordCarArray = new[] {new FordCar {Name = "Dans Ford Car"}, new FordCar {Name = "Beccis Ford Car"}};

            IEnumerable<FordCar> enumerableFordCars = fordCarArray;            
            GiveCarsAWash(enumerableFordCars);


            Console.ReadLine();
        }


        static void GiveCarsAWash(IEnumerable<Car> carsToWash)
        {
            foreach (var car in carsToWash)
            {
                Console.WriteLine("Washing " + car.Name);
            }
        }

        static void GiveCarsAParkingTicket(Car[] carsWhoDeserveATicket)
        {
            carsWhoDeserveATicket[0] = new VauxhallCar{Name = "Dans Vauxhall Car"};
            foreach (var car in carsWhoDeserveATicket)
            {
                Console.WriteLine("Ha " + car.Name + " you're getting a Parking Ticket");
            }
            
        }
        static void PerformHandbrakeTurn(Car car)
        {
            Console.WriteLine("Performing Handbrake Turn on " + car.Name);
        }
    }
}

Covariance in C# 4 (.Net 4.0) with IEnumerable<Car>

So you're annoyed that didn't work, now do the simplest thing, change the project from .Net 3.5 to .Net 4.0 now try and compile....

Yes it works excellent. This demonstrates that they've fixed one of those niggly things that you'd notice, or to quote Anders Hejlsberg

(Allowing) you to do things in your code that previously you were surprised you can't do

What about this old chestnut:

IList<FordCar> fordCars = new List { new FordCar { Name = "Dans Ford Car" }, new FordCar { Name = "Beccis Ford Car" } };
IEnumerable<Car> cars = fordCars;

Such a simple thing, yes a List of FordCar is assignable to a IEnumerable of Car, but NO...

I don't know about you but, I HATE this particular exception, no I'm not missing a cast, deal with it you stupid thing. Thank goodness this is fine in C# 4. The cursing this will save me.

What makes Covariance work in C# 4?

If you look carefuly you'll find that if you look at specifically: interfaces and delegates have a new modifier out on them.

You'll notice all your favourites like: IEnumerable<T> and IQueryable<T> now have the magic out modifier on them.

How can I make my Interfaces Covariant?

Well simple you add the out modifier to your interface definition. Imagine this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Variance.Net40
{
    class Program
    {
        interface ICanBeDriven<out T> where T : Car
        {
            T Item { get; }
            void Drive();
        }

        class CanBeDriven<T>:ICanBeDriven where T : Car 
        {
            public CanBeDriven(T item)
            {
                Item = item;
            }

            public T Item { get; private set; }
            public void Drive()
            {
                Console.WriteLine("Driving " + Item.Name);
            }
        }

        abstract class Car { public string Name { get; set; } }
        class FordCar : Car{}
        class VauxhallCar : Car { }

        static void Main(string[] args)
        {
            var carToDrive = new CanBeDriven<FordCar>(new FordCar { Name = "Dans Ford Cars" });

            DriveCar(carToDrive);

            Console.ReadLine();
        }

        static void DriveCar(ICanBeDriven<T> carToDrive)
        {
            carToDrive.Drive();
        }



    }
}

The above will fail to compile, without the out modifier on the ICanBeDriven<out T> interface declaration.

Covariant out modifier only works for Interfaces and Delegates

Please remember, that the covariant modifier can only be used on interfaces and delegates, this means that:

 

static void Main(string[] args)
        {
            var carToDrive = new CanBeDriven<FordCar>(new FordCar { Name = "Dans Ford Cars" });
            CanBeDriven<Car> canAlsoDrive = carToDrive;

            DriveCar(carToDrive);

            Console.ReadLine();
        }

Will fail to compile because you're defining a concrete type CanBeDriven<Car> which don't support covariance, so be warned.

Contravariance in C# 4

Covariance tends to happen in delegates, or where we perform actions. It is basically the reverse of covariance, where we push something from the more abstract type, in our case Car to the more specific type FordCar, so in the example below we're saying the implementation of DriveFaster for Car is also good enough for FordCar whereas with covariance we're always converting the FordCar into a Car.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Variance.Net40
{
    public abstract class Car { public string Name { get; set; } }
    public class FordCar : Car { }
    public class VauxhallCar : Car { }

    class Program
    {
        delegate void DriveFaster<in T1,in T2>(T1 t1, T2 t2);

        static void Main(string[] args)
        {
            var ford = new FordCar { Name = "Dans Ford Car" };

            DriveFaster<Car,string> driveFast = (canBeDrivenTypeParameter, stringTypeParameter)
                => Console.WriteLine(canBeDrivenTypeParameter.Name + " " + stringTypeParameter);

            DriveFaster<FordCar,string> driveFordsFaster = driveFast;
            
            driveFordsFaster(ford, "Faster");
            Console.ReadLine();
        }
        
    }


}

What's been changed in .Net 4.0?

A fair bit including:

  • IComparer<in T>
  • IEqualityComparer<in T>
  • Action<in T>
  • Predicate<in T>
  • EventHander<in T>

Conclusion

  • Covariance is the in modifer
  • Contravariance is the out modifier
  • Contravariance can only be used on interfaces and delegates
  • Variance can only be used on reference types so no value types allowed.
  • Arrays are still broken even in .Net 4.0

References

http://channel9.msdn.com/Shows/Going+Deep/Inside-C-40-dynamic-type-optional-parameters-more-COM-friendly

http://stackoverflow.com/questions/245607/how-is-generic-covariance-contra-variance-implemented-in-c-4-0

http://andersnoras.com/post/100795246/c-4-0-covariance-and-contra-variance

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

http://blogs.msdn.com/b/ericlippert/archive/2009/10/26/some-new-videos.aspx

http://blog.mbharrington.org/2011/02/21/covariance-and-contravariance-in-net-4/

http://msdn.microsoft.com/en-us/vcsharp/ee672314.aspx -- Part 1

http://msdn.microsoft.com/en-us/vcsharp/ee672319.aspx -- Part 2

 



Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2013