Covariance and Contravariance in C# 4.0

C# 4.0 introduces the notion of Covariance and Contravariance of generic type parameters for interfaces and delegate types. Eric Lippert has put together a bunch of posts that goes into details of the why and how, an excellent read but not for the faint of heart. I would strongly suggest reading these posts to get a firm grounding and a better appreciation of this feature. It took me a while to get my head wrapped around this, especially since none of the VS2010 Betas were not out at the time and there was no way to try out any code. Now that I have Beta2 of VS2010 at hand I decided to try out some code samples first hand and solidify my understanding of the feature.

Suppose we have the following inheritance hierarchy:

 
  1. interface IAnimal  
  2. {  
  3.     void Speak();  
  4. }  
  5.   
  6. class Whale : IAnimal  
  7. {  
  8.      public void  Speak()  
  9.     {  
  10.          Console.WriteLine("I'm a Whale");  
  11.     }  
  12. }  
  13.   
  14. class Giraffe : IAnimal  
  15. {  
  16.     public  void Speak()  
  17.     {  
  18.         Console.WriteLine("I'm a Giraffe");  
  19.     }  
  20. }  

 

and a processor to process animals:

 
  1. interface IProcessor<T>  
  2. {  
  3.     void Process(IEnumerable<T> ts);     
  4. }  
  5.   
  6. class Processor<T> : IProcessor<T> where T:IAnimal  
  7. {  
  8.     public void Process(IEnumerable<T> ts)  
  9.     {  
  10.         foreach (var t in ts)  
  11.         {  
  12.             t.Speak();  
  13.         }  
  14.     }          
  15. }  

 

It would be natural to be able to create a Processor to process any kind of animal, but the following code does not compile today on the C# 3.0 compiler

 
  1. List<Giraffe> giraffeList = new List<Giraffe> { new Giraffe() };  
  2. IProcessor<IAnimal> proc = new Processor<IAnimal>();  
  3. proc.Process(giraffeList);  
We have to add a silly"OfType<IAnimal>" cast to make it work:
 
  1. List<Giraffe> giraffeList = new List<Giraffe> { new Giraffe() };  
  2. IProcessor<IAnimal> proc = new Processor<IAnimal>();  
  3. proc.Process(giraffeList.OfType<IAnimal>());  

 

In C# 4.0 no such cast is needed and everything just works as expected. Why? because in .NET 4.0 IEnumerable<T> is covariant in it's type parameter T, this is what the definition looks like

 
  1. public interface IEnumerable<out T> : IEnumerable  
  2. {  
  3. ....  
  4. ....  
  5. }   
The "out" keyword is what makes the "T" covariant; which means that you can pass in any type which is more derived than "T" as the type argument, hence we can pass in IEnumerable<Giraffe> or IEnumerable<Whale> to the Process method which accepts an IEnumerable<IAnimal> and everything just works as expected in a type safe way.

 

You can define your own covariant types to allow for such conversions.
Consider the following generic class definition to create instances of a specified type
 
  1. interface IFactory<out T>  
  2. {  
  3.     T CreateInstance();  
  4. }  
  5.   
  6. class Factory<T> : IFactory<T> where T: new()  
  7. {  
  8.     public T CreateInstance()  
  9.     {  
  10.         return new T();  
  11.     }          
  12. }  
IFactory<T> is covariant in T so we can treat a Factory<Giraffe> as an IFactory<IAnimal>. This allows us to treat factories of types in the same inheritance chain uniformly like so:
 
  1. IFactory<IAnimal> giraffeFactory = new Factory<Giraffe>();  
  2. IFactory<IAnimal> whaleFactory = new Factory<Whale>();  
  3.   
  4. List<IFactory<IAnimal>> factories = new List<IFactory<IAnimal>>{ giraffeFactory, whaleFactory };  
  5. foreach (var factory in factories)  
  6. {  
  7.     factory.CreateInstance().Speak();  
  8. }  

Note that it is illegal for a covariant type parameter to occur in an input position, it can only occur in output positions such as return type of a method in the type. This restriction is key in making covariance work,allowing the type parameter in an input position would break covariance. How you might ask. Imagine if the following illegal code were legal and you could specify T in an input position.

 
  1. interface IFactory<out T>    
  2. {  
  3.     T CreateInstance();  
  4.     T CreateInstanceFrom(T t);  
  5. }  
  6.   
  7. class Factory<T> : IFactory<T>  
  8.     where T : new()  
  9.     where T : ICloneable  
  10. {  
  11.     public T CreateInstance()  
  12.     {  
  13.         return new T();  
  14.     }  
  15.     public T CreateInstanceFrom(T t)  
  16.     {  
  17.         return (T)t.Clone();  
  18.     }  
  19.   
  20. }  
We added a constraint to the factory class to constrain the type parameter to support ICloneable and added a new method CreateInstanceFrom which accept the instance to clone. Now what happens if we do the following:
 
  1. IFactory<IAnimal> giraffeFactory = new Factory<Giraffe>();  
  2. giraffeFactory.CreateInstanceFrom(new Whale())  
Oh my! we just passed in a Whale to clone a Giraffe, and we'd bomb a runtime.
It is precisely for this reason that a covariant type parameter can only occur in output positions(hence the keyword out) and the runtime error is now a compile time error.

 

But what if we wanted to have the type paramter in an input position to get the benefits variance has to offer, enter contrvariance which is the dual of covariance. In that a type argument can be safely replaced by a more derived type but only in an input position.

If T appears ONLY in an input position (such as a parameter in methods of IProcessor) then we can mark T as contravariant using the in keyword, allowing us to substitute any sub type of T.

 
  1. interface IProcessor<in T>  
  2. {  
  3.     void Process(IEnumerable<T> ts);  
  4. }  

This makes assignments such as the following legal.

 
  1. List<Giraffe> giraffes = new List<Giraffe> { new Giraffe() };  
  2. List<Whale> whales = new List<Whale> { new Whale() };  
  3. IProcessor<IAnimal> animalProc = new Processor<IAnimal>();  
  4. IProcessor<Giraffe> giraffeProcessor = animalProc;  
  5. IProcessor<Whale> whaleProcessor = animalProc;  
  6. giraffeProcessor.Process(giraffes);  
  7. whaleProcessor.Process(whales);  

 

Finally let's look at variance in delegate types. Consider the following delegate definition and it's subsequent usage

 
  1. public delegate void MyAction<in T>(T t);  
  2. ........  
  3. .......  
  4. .........  
  5.  MyAction<IAnimal> animalAction = a => a.Speak();  
Since T is contravariant, we can treat MyAction<IAnimal> as MyAction<Giraffe> since Giraffe is more derived than IAnimal
  1. MyAction<Giraffe> rafAction = animalAction;  

 

Now let's combine both co and contravariance, consider the following delegate

 
  1. public delegate T2 MyFunc<in T1,out T2>(T1 t1);  
T2 is covariant and T1 is contravariant and hence we can use T1 or any subtypes of T1 as the first generic type paramter and T2 or any of its base types as the second generic type parameter, allowing us to use assignments such as the following with full type safety
 
  1. Func<IAnimal, Giraffe> func = a => a as Giraffe;  
  2. Func<Giraffe, IAnimal> funcG = func;  

 

Covariance and Contravariance may sound like a mouthful and daunting to understand but at its core it's quite simple to grasp once you understand its purpose, it enables writing generic code more naturally and allows safe type coercions. That's it for today. Happy coding.

Print | posted @ Sunday, January 10, 2010 12:50 PM

Comments on this entry:

Gravatar # re: Covariance and Contravariance in C# 4.0
by Bill Craun at 1/16/2010 7:02 AM

Awesome, concise explanation and one of the best I've seen so far explaining covariance and contravariance.
Gravatar # re: Covariance and Contravariance in C# 4.0
by Anton at 1/16/2010 8:11 PM

A good explanation, but I think the whole Covariance and Contravariance concept will be ignored by most developers for being too complex.
Gravatar # re: Covariance and Contravariance in C# 4.0
by Jay at 3/19/2010 2:50 PM

Very nice post.
Most articles I've read so far have been far too complicated but this one is straight forward and concise.
Gravatar # re: Covariance and Contravariance in C# 4.0
by SHRUTI at 4/27/2010 2:37 AM

cool 1!!!!!!!!
Gravatar # re: Covariance and Contravariance in C# 4.0
by Rajesh at 6/17/2010 11:21 PM

yes this is cool but i find this link better:
http://www.aspboy.com/Categories/CSharpArticles/DelegateCovarianceAndContravarianceExplained.aspx
Gravatar # re: Covariance and Contravariance in C# 4.0
by Shweta at 9/4/2010 9:47 AM

Very good explanation
Gravatar # re: Covariance and Contravariance in C# 4.0
by Mario at 8/11/2011 9:38 PM

A Nice blog. Simple to understand
Gravatar #  Covariance and Contravariance in C# 4.0
by Sattha at 10/13/2011 9:30 PM

I admire good writing but lazy enough to write comment. Your writing compelled me to say 'Boy ..pat on your back'...its really good ...keep the good work up.
Gravatar # re: Covariance and Contravariance in C# 4.0
by Aliyu at 2/1/2012 7:38 AM

Wow... nicest explanation about covariance and contravariance... cloning whale to giraffe had me laughing my lungs out.
Gravatar # re: Covariance and Contravariance in C# 4.0
by Fazil at 5/31/2012 10:01 AM

Way to go. I was actually spinning my head to understand this concept w.r.t.o real world scenario, But ur explanation here is just wat the doctor ordered for me. Thanks mate. Keep up the good work
Post A Comment
Title:
Name:
Email:
Comment:
Verification: