Monday, August 09, 2010

Covariance and Contravariance for Delegates in C#

.NET 4 introduces new usages for covariance and contravariance.

From MSDN:

Covariance and contravariance provide a degree of flexibility when matching method signatures with delegate types. Covariance permits a method to have a more derived return type than what is defined in the delegate. Contravariance permits a method with parameter types that are less derived than in the delegate type.

What helps me understand this variance stuff is to base it around the "is a" relationship.

class A {}
class B : A{}
class C: B{}

A "is a" B => false
A "is a" C => false

B "is a" A => true
B "is a "C => false

C "is a" A => true
C "is a" B => true

Return type of delegate signatures (Covariance)

public delegate A HandlerMethodA();

The return type of the delegate is "A".

Speaking solely about the return type in this example, any method that returns something that "is a" A can be used as the delegate method.

public static A FirstHandler() { return null; }
public static B SecondHandler(){return null;}
public static C ThirdHandler(){return null;}

All three of these methods can be used as a delegate method for delegate HandlerMethodA.

public delegate B HandlerMethodB();

HandlerMethodB signature has the return type of B. Therefore only SecondHandler and ThirdHandler can be used as methods for the delegate HandlerMethodB.


Parameter types of delegate signatures (Contravariance)

Using the same classes above, A, B, and C, we define a new delegates:

public delegate void HandlerMethodC(C c);

public static void FirstHandler(A a) { }
public static void SecondHandler(B b){}
public static void ThirdHandler(C c){}

All three of these methods can be used as a delegate method for delegate HandlerMethodC.

The reason is that a variable of type C can be passed to any of these handlers.
The delegate signature parameter "is a" delegate method parameter.
C "is a" A is true.
B "is a" A is true.
A "is a" A is true.

Consider another delegate:
public delegate void HandlerMethodB(B b);

Only methods that can accept B as the parameter type can be assigned to the delegate HandlerMethodB.

FirstHandler and SecondHandler methods may be assigned to the delegate HandlerMethodB.
FirstHandler method can accept B types as well as A and C.
SecondHandler method can accept B types as well as C.


ThirdHandler cannot be assigned to delegate HandlerMethodB because there is no overload for ThirdHandler that matches the delegate.
ThirdHandler method can only accept C types.
Since the delegate signature is of type B, third handler cannot be used.

Regardless of which methods may be assigned to HandlerMethodC or HandlerMethodB the delegate will only accept variables that "are" of the type defined by the delegate's signature.

HandlerMethodC may only be invoked with a parameter of type C.
HanlderMethodB may be invoked with a parameter of type C or B.


I recommend reading:
http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx

http://geekswithblogs.net/Martinez/articles/covariance-contravariance-and-invariance-in-c-language.aspx


Here is my code I used to better understand Convariance and Contravariance for Delegates in C#.


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

namespace VarianceExperiments
{
class A
{
}

class B : A
{
}

class C : B
{
}

class Program
{
public delegate A CovarianceHandlerA( );
public delegate B CovarianceHandlerB( );
public delegate C CovarianceHandlerC( );

public delegate void ContravarianceHandlerA( A a );
public delegate void ContravarianceHandlerB( B b );
public delegate void ContravarianceHandlerC( C c );

public static A CoHandlerA( ) { return new A( ); }
public static B CoHandlerB( ) { return new B( ); }
public static C CoHandlerC( ) { return new C( ); }

public static void ContraHandlerA( A a ) { }
public static void ContraHandlerB( B b ) { }
public static void ContraHandlerC( C c ) { }

static void Main( string[] args )
{
A a = new A();
B b = new B();
C c = new C();

Console.WriteLine( "A is a B => {0}", ( a is B ) );
Console.WriteLine( "A is a C => {0}", ( a is C ) );

Console.WriteLine( "B is a A => {0}", ( b is A ) );
Console.WriteLine( "B is a C => {0}", ( b is C ) );

Console.WriteLine( "C is a A => {0}", ( c is A ) );
Console.WriteLine( "C is a B => {0}", ( c is B ) );


CovarianceHandlerA coHandler = CoHandlerA;
A coResult = coHandler( );
Console.WriteLine( "coResults is a A => {0}, is a B => {1},
is a C => {2}", ( coResult is A ),
( coResult is B ), ( coResult is C ) );

coHandler = CoHandlerB;
coResult = coHandler( );
Console.WriteLine( "coResults is a A => {0}, is a B => {1},
is a C => {2}", ( coResult is A ),
( coResult is B ), ( coResult is C ) );

coHandler = CoHandlerC;
coResult = coHandler( );
Console.WriteLine( "coResults is a A => {0}, is a B => {1},
is a C => {2}", ( coResult is A ),
( coResult is B ), ( coResult is C ) );

//---------------------------------------------------------------

ContravarianceHandlerC contraHandlerC = ContraHandlerA;
//contraHandlerC( a ); //Error: Argument 1: cannot convert A to C
//contraHandlerC( b ); //Error: Argument 1: cannot convert B to C
contraHandlerC( c );

contraHandlerC = ContraHandlerB;
//contraHandlerC( a ); //Error: Argument 1: cannot convert A to C
//contraHandlerC( b ); //Error: Argument 1: cannot convert B to C
contraHandlerC( c );

contraHandlerC = ContraHandlerC;
//contraHandlerC( a ); //Error: Argument 1: cannot convert A to C
//contraHandlerC( b ); //Error: Argument 1: cannot convert B to C
contraHandlerC( c );

//---------------------------------------------------------------

ContravarianceHandlerB contraHandlerB = ContraHandlerA;
//contraHandlerB( a ); //Error: Argument 1: cannot convert A to B
contraHandlerB( b );
contraHandlerB( c );

contraHandlerB = ContraHandlerB;
//contraHandlerB( a ); //Error: Argument 1: cannot convert A to B
contraHandlerB( b );
contraHandlerB( c );

//contraHandlerB = ContraHandlerC; //No overload for ContrHandlerC matches delegate ContravarianceHandlerB

}
}
}

No comments: