Tuesday, August 10, 2010

Covariance and Contravariance and Generic Delegates

Generic Delegates now use the modifiers "in" and "out" with .NET 4.

"Out" is associated with return values and covariance.
"In" is associated with input parameters(method arguments) and contravariance.

I use the following classes and relationships throughout this blog post.


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

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

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

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

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


Covariance


//-----------------------------------------------
public delegate R DCovariant<out R>( );

//-----------------------------------------------
//Methods that match the Covariant Signature

public static object CovariantObjectMethod( )
{
return new object( );
}

public static A CovariantAMethod( )
{
return new A( );
}

public static B CovariantBMethod( )
{
return new B( );
}

public static C CovariantCMethod( )
{
return new C( );
}


Delegate "DCovariant" is an example of using "out" to specify a covariant delegate. Covariance is concerned with the return type of the delegate.

Any covariant delegate can be assigned any method that matches the signature of the delegate and the return type of the method satisfies the "is a" relationship with the return type of the delegate.


DCovariant<object> dCovObj = CovariantObjectMethod;
DCovariant<A> dCovA = CovariantAMethod;
DCovariant<B> dCovB = CovariantBMethod;
DCovariant<C> dCovC = CovariantCMethod;

dCovObj = CovariantAMethod;
dCovObj = CovariantBMethod;
dCovObj = CovariantCMethod;

//dCovA = CovarianteObjectMethod; //wrong return type.
dCovA = CovariantBMethod;
dCovA = CovariantCMethod;

//dCovB = CovarianteObjectMethod; //wrong return type.
//dCovB = CovarianteAMethod; //wrong return type.
dCovB = CovariantCMethod;

//dCovC = CovarianteObjectMethod; //wrong return type.
//dCovC = CovarianteAMethod; //wrong return type.
//dCovC = CovarianteBMethod; //wrong return type.


Consider the declaration:
DCovariant<object> dCovObj

This delegate's return type is Object, so therefore any method that matches the delegate signature and returns something that is an object may be assigned to dConObj.

DCovariant<A> dCovA can be assigned any method that matches the delegate signature and returns something that "is a" A.

DCovariant<B> dCovB can be assigned any method that matches the delegate signature and returns something that "is a" B.

DCovariant<C> dCovC can be assigned any method that matches the delegate signature and returns something that "is a" C.

Contravariance


//-----------------------------------------------
public delegate void DContravariant<in T>( T t );

//-----------------------------------------------
//Methods that match the Contravariant Signature

public static void ContravariantObjectMethod( Object o )
{
}

public static void ContravariantAMethod( A a )
{
}

public static void ContravariantBMethod( B b )
{
}

public static void ContravariantCMethod( C c )
{
}


Above are declared a contravariant delegate using the "in" modifier and four methods that match the signature of the contravariant delegate.

Below I have declared and assigned four DContravariant delegates. The types used are Object, A, B, and C.


//Contravariant
DContravariant<object> dContravarObj = ContravariantObjectMethod;
DContravariant<A> dContravarA = ContravariantAMethod;
DContravariant<B> dContravarB = ContravariantBMethod;
DContravariant<C> dContravarC = ContravariantCMethod;

Below I have declared and instantiated an object of each type and then invoke each delegate with each type.

Object o = new Object( );
A a = new A( );
B b = new B( );
C c = new C( );

dContravarObj( o );
dContravarObj( a );
dContravarObj( b );
dContravarObj( c );

//dContravarA( o ); //Argument 1: cannot convert from...
dContravarA( a );
dContravarA( b );
dContravarA( c );

//dContravarB( o ); //Argument 1: cannot convert from...
//dContravarB( a ); //Argument 1: cannot convert from...
dContravarB( b );
dContravarB( c );

//dContravarC( o ); //Argument 1: cannot convert from...
//dContravarC( a ); //Argument 1: cannot convert from...
//dContravarC( b ); //Argument 1: cannot convert from...
dContravarC( c );

Notice that there are compile time errors where the input argument could not be converted to the proper type.

dContravarObj can accept all of the variables as arguments because they all satisfy the "is a" relationship with Object.

dContravarA can accept variables of type Object and A.
dContravarB can accept variables of type Object, A, and B.
dContravarC can accept variables of type Object, A, B, and C.

The contraviance and the use of "in" plays its role when assigning methods with different signatures to the delegate. Below are the results.


//dContravarObj = ContravariantAMethod; //no overload matches delegate...
//dContravarObj = ContravariantBMethod; //no overload matches delegate...
//dContravarObj = ContravariantCMethod; //no overload matches delegate...

dContravarA = ContravariantObjectMethod;
//dContravarA = ContravariantBMethod; //no overload matches delegate...
//dContravarA = ContravariantCMethod; //no overload matches delegate...

dContravarB = ContravariantObjectMethod;
dContravarB = ContravariantAMethod;
//dContravarB = ContravariantCMethod; //no overload matches delegate...

dContravarC = ContravariantObjectMethod;
dContravarC = ContravariantAMethod;
dContravarC = ContravariantBMethod;



Notice that any method may be assigned to any delegate if:
The delegate's input argument satisfies the "is a" relationship with the method's input argument.

Finally to tie this all together I will use the most derived class "C" and the corresponding delegate and assign it the method with the lease derived class "Object".


//Invocations
dContravarC = ContravariantObjectMethod;
//dContravarC( o ); //Argument 1: cannot convert from...
//dContravarC( a ); //Argument 1: cannot convert from...
//dContravarC( b ); //Argument 1: cannot convert from...
dContravarC( c );

Eventhough the ContravarianteObjectMethod can take any of our classes as input the delegate signature for dContravarC is DContravariant<C> and therefore the method invocation can only accept objects that "is a" C.

To help illustrate this I will assign dContravarB since class B is in the "middle" of the derivation.

dContravarB = ContravariantObjectMethod;
//dContravarB( o ); //Argument 1: cannot convert from...
//dContravarB( a ); //Argument 1: cannot convert from...
dContravarB( b );
dContravarB( c );


Notice that the delegate invocation will only accept things that satisfy the "is a" relationship with class B, which is class B and class C.

Following is the complete listing of the example code:

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

namespace GenericDelegateModifiers
{
class A { }
class B : A { }
class C : B { }

class Program
{
//-----------------------------------------------
public delegate R DCovariant<out R>( );

//-----------------------------------------------
//Methods that match the Covariant Signature

public static object CovariantObjectMethod( )
{
return new object( );
}

public static A CovariantAMethod( )
{
return new A( );
}

public static B CovariantBMethod( )
{
return new B( );
}

public static C CovariantCMethod( )
{
return new C( );
}

//-----------------------------------------------
public delegate void DContravariant<in T>( T t );

//-----------------------------------------------
//Methods that match the Contravariant Signature

public static void ContravariantObjectMethod( Object o )
{
}

public static void ContravariantAMethod( A a )
{
}

public static void ContravariantBMethod( B b )
{
}

public static void ContravariantCMethod( C c )
{
}

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

static void Main( string[] args )
{
//-----------------------------------------------
//Covariant
DCovariant<object> dCovObj = CovariantObjectMethod;
DCovariant<A> dCovA = CovariantAMethod;
DCovariant<B> dCovB = CovariantBMethod;
DCovariant<C> dCovC = CovariantCMethod;

dCovObj = CovariantAMethod;
dCovObj = CovariantBMethod;
dCovObj = CovariantCMethod;

//dCovA = CovarianteObjectMethod; //wrong return type.
dCovA = CovariantBMethod;
dCovA = CovariantCMethod;

//dCovB = CovarianteObjectMethod; //wrong return type.
//dCovB = CovarianteAMethod; //wrong return type.
dCovB = CovariantCMethod;

//dCovC = CovarianteObjectMethod; //wrong return type.
//dCovC = CovarianteAMethod; //wrong return type.
//dCovC = CovarianteBMethod; //wrong return type.

//-----------------------------------------------
//Contravariant
DContravariant<object> dContravarObj = ContravariantObjectMethod;
DContravariant<A> dContravarA = ContravariantAMethod;
DContravariant<B> dContravarB = ContravariantBMethod;
DContravariant<C> dContravarC = ContravariantCMethod;

Object o = new Object( );
A a = new A( );
B b = new B( );
C c = new C( );

dContravarObj( o );
dContravarObj( a );
dContravarObj( b );
dContravarObj( c );

//dContravarA( o ); //Argument 1: cannot convert from...
dContravarA( a );
dContravarA( b );
dContravarA( c );

//dContravarB( o ); //Argument 1: cannot convert from...
//dContravarB( a ); //Argument 1: cannot convert from...
dContravarB( b );
dContravarB( c );

//dContravarC( o ); //Argument 1: cannot convert from...
//dContravarC( a ); //Argument 1: cannot convert from...
//dContravarC( b ); //Argument 1: cannot convert from...
dContravarC( c );

//dContravarObj = ContravariantAMethod; //no overload matches delegate...
//dContravarObj = ContravariantBMethod; //no overload matches delegate...
//dContravarObj = ContravariantCMethod; //no overload matches delegate...

dContravarA = ContravariantObjectMethod;
//dContravarA = ContravariantBMethod; //no overload matches delegate...
//dContravarA = ContravariantCMethod; //no overload matches delegate...

dContravarB = ContravariantObjectMethod;
dContravarB = ContravariantAMethod;
//dContravarB = ContravariantCMethod; //no overload matches delegate...

dContravarC = ContravariantObjectMethod;
dContravarC = ContravariantAMethod;
dContravarC = ContravariantBMethod;

//Invocations
dContravarC = ContravariantObjectMethod;
//dContravarC( o ); //Argument 1: cannot convert from...
//dContravarC( a ); //Argument 1: cannot convert from...
//dContravarC( b ); //Argument 1: cannot convert from...
dContravarC( c );

dContravarB = ContravariantObjectMethod;
//dContravarB( o ); //Argument 1: cannot convert from...
//dContravarB( a ); //Argument 1: cannot convert from...
dContravarB( b );
dContravarB( c );

}
}
}

No comments: