Covariance & Contravariance for Generic Interfaces & Delegates in C# 4.0

kick it on DotNetKicks.com
StumbleUpon.com
Save to delicious Saved by 0 users



Explain the Covariance and Contravariance for Generic Interfaces and Delegates in C# 4.0.


Covariance and Contravariance support are not new in C#.  Let look at the following hypothetical C# delegates in .NET Framework 3.5 and Visual Studio 2008.

1:  public abstract class Vehicle  
2:  {  
3:    public int Speed { get; set; }  
4:    public int Color { get; set; }  
5:    public string RegNo { get; set; }  
6:  }  
7:    public class Car : Vehicle { }  
8:    public class Truck : Vehicle { }  
9:    public class MotorCycle : Vehicle { }  
10:   public delegate Vehicle CloneCar (Car car); //delegate  
11:   public delegate V Clone<T,V> (T t); //generic delegate  
12:   class Program  
13:   {  
14:      public static Car CloneCarMethod(Vehicle vechicle)  
15:      {  
16:           return new Car();  
17:      }  
18:      public static Car CloneVehicleMethod(Vehicle vehicle)  
19:      {  
20:           return new Car();  
21:      }  
22:      private static CloneCar _nonGenericCloneCar = null;  
23:      private static Clone _genericCloneCar = null;  
24:     static void Main(string[] args)  
25:     {  
26:       _nonGenericCloneCar = new CloneCar(CloneCarMethod); // short cut: CloneCarMethod  
27:       _genericCloneCar = new Clone(CloneVehicleMethod); //short cut: CloneVehicleMethod  
28:     }  
29:  }  


In the above example, when we create new delegates from the CloneCarMethod and CloneVechileMethod methods, Visual Studio 2008 provide variance support for matching the method signatures with delegate types. When a static method, CloneCarMethod with a more derived return type, Car and less derived input type, Vehicle is assigned to nonGenericCloneCar, an implicit conversion takes place such that the delegate constructor for CloneCar not only can take method with same signatures but methods which have signatures that are covariance and contravariance to the delegate types.

This not only works for the non generic delegates but it works for the generic delegates as well.
However, the implicit conversion is provided when we try to assign method to the delegate. The implicit conversion is not provided when we try to assign delegates that have different types to each other. Please look at the following code segment, the compiler will complain when encountering this statement, _genericCloneCar2 = _genericCloneCar1 in .NET Framework 3.5.


1:   private static CloneCar _nonGenericCloneCar = null;  
2:   private static Clone _genericCloneCar = null;  
3:   private static Clone _genericCloneCar1 = null;  
4:   private static Clone _genericCloneCar2 = null;  
5:   static void Main(string[] args)  
6:   {  
7:     _nonGenericCloneCar = new CloneCar(CloneCarMethod); // short cut: CloneCarMethod  
8:     _genericCloneCar = new Clone(CloneVehicleMethod); //short cut: CloneVehicleMethod  
9:     _genericCloneCar1 = new Clone(CloneVehicleMethod);  
10:    //_genericCloneCar2 = _genericCloneCar1; // C# 3.0 compiler won't compile  
11:   }  

Code Download: Covariance and Contravariance in VS2008. (This application sample requires Visual Studio 2008 that can open C# project. Right click on the above link and choose Save Target As. Rename the extension from .pdf to .zip once you have downloaded the file. Let me know if you have any problem running the code.)

In .NET Framework 4.0,  implicit conversion between delegates has been made possible by explicitly declaring generic parameters as covariant or  contravariant through the use of in or out modifier. So that the delegates that have different type parameters can be assigned to each other.  If you observe the .NET Framework 4.0 APIs through the object browser in Visual Studio 2010, you will notice the .NET Framework 4.0 has introduced support for covariance and contravariance support  for some of the existing  interfaces and delegates through the use of  in and out modifiers.



In Visual Studio 2010, the previous code segment can be written as follow :

1:   public delegate Vehicle CloneCar(Car car); //delegate  
2:   public delegate V Clone<in T,out V>(T t); //generic delegate  
3:   class Program  
4:   {  
5:    public static Car CloneCarMethod(Vehicle vechicle)  
6:    {  return new Car();  }  
7:    public static Car CloneVehicleMethod(Vehicle car)  
8:    {  return new Car();   }  
9:    private static CloneCar _nonGenericCloneCar = null;  
10:   private static Clone _genericCloneCar = null;  
11:   private static Clone _genericCloneCar1 = null;  
12:   private static Clone _genericCloneCar2 = null;  
13:   static void Main(string[] args)  
14:   {  
15:     _nonGenericCloneCar = new CloneCar(CloneCarMethod); // short cut: CloneCarMethod  
16:     _genericCloneCar = new Clone(CloneVehicleMethod); //short cut: CloneVehicleMethod  
17:     _genericCloneCar1 = new Clone(CloneVehicleMethod);  
18:     _genericCloneCar2 = _genericCloneCar1; // C# 4.0 will compile because of the variance support through the in and out modifier in the delegate declaration  
19:   }  
20:  }  

By introducing the in and out modifiers for the generic type parameters in the Clone delegate declaration , the statement _genericCloneCar2 = _genericCloneCar1 become valid.

Code Download: Covariance and Contravariance in VS2010. (This application sample requires Visual Studio 2010 that can open C# project. Right click on the above link and choose Save Target As. Rename the extension from .pdf to .zip once you have downloaded the file. Let me know if you have any problem running the code.)

The in modifier is used to achieve the contravariance. The type parameter that has the in modifier is a contravariant type and can be used only in input position in the interface
and delegate.

The out modifier is used to achieve the covariance. The type parameter that has the out modifier is a covariant type and can be used only in output position in the interface and
delegate.

Both the covariance and contravariance available only to interface and delegate. Let look at the following samples of how the covariance and contravariance are used in the interfaces and delegates.

1:  //Not valid as the contravariant type T occurs in output position of the delegate  
2:  public delegate T CloneVehicle<in T>();  
3:  //Not valid as the covariant type T occurs in the input position of the delegate  
4:  public delegate void CloneCar<out T>(T t);  
5:  interface IVehicleCovariant<out T>  
6:  {  
7:    //Valid  
8:    T Build();  
9:    //Not valid as T is a Covariant type which can only occur in output position only  
10:   void Repair(T t);  
11:   T Value  
12:   {  
13:     get; //Valid  
14:     //Not valid as T is a Covariant type which can only occur in output position only  
15:     set;  
16:   }  
17: }  
18: interface IVehicleContravariant<in T>  
19: {  
20:    //Not valid as T is a Contravariant type which can only occur in input position only  
21:    T Build();  
22:    //Valid  
23:    void Repair(T t);  
24:    T Value  
25:    {  
26:     //Not valid as T is a Contravariant type which can only occur in input position only  
27:     get;  
28:     //Valid  
29:     set;  
30:    }  
31: }  

With the support of variance in generic type parameters, the following code assignments are valid and legal in Visual Studio 2010 and .NET Framework 4.0.

1:  IEnumerable<Object> objects = new List<String>();  

The designer of .NET Framework 4.0 has put the covariance support for the generic interface IEnumerable. The reason is that there is no way for us to modify the objects sequence collection which can then affect the type safety of the instance List collection. With IEnumerable collection, we can only loop through its collection to get the individual item. There is no way for us to modify its content. Variance is about assignments such as this in which case the type safety rule is not broken.

If we look at the generic interface ICollection, the covariance support is not available for the type parameter T because allowing such a support will break the type safety rule. Assuming the following is valid and is allowed by the .NET Framework 4.0,

1:    ICollection<Object> objects = new List<String>();  
2:    objects.Add(1);  

Then the statement objects.Add(1) will add an integer value to a list of string and subsequently retrieved as string has definitely breached the .NET type safety rule.

At last, the covariance and contravariance only apply to reference type. Value type can be used in the generic type parameter,  but there will be no inheritance relationships for it.

See you in my next post.
Bye and stay tune...


1 comments:

  Anonymous

March 22, 2010 at 1:01 AM

Nice & thanks

Post a Comment