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 );

}
}
}

Monday, August 09, 2010

Covariance and Contravariance and Generic Interfaces

Generic Interfaces 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.

Covariance

// Covariant interface.
interface ICovariant<out R> { }

// Extending covariant interface.
interface IExtCovariant<out R> : ICovariant<R> { }

// Implementing covariant interface.
class SampleCovariant<R> : ICovariant<R> { }


The generic type parameter has "out" as a modifier. The "out" declaration can be used if the type parameter is used only as a return type of interface methods and not used as a type of method arguments.



ICovariant<Object> covObj = new SampleCovariant<Object>( );
ICovariant<String> covStr = new SampleCovariant<String>( );

//covStr = covObj; //Cannot implicitly convert type ICovariant<object> to ICovariante<string>. An explicit conversion exists (are you mssing a cast?)

// You can assign covStr to covObj because the ICovariant interface is covariant.
covObj = covStr;

String "is a" Object => true
Object "is a" String => false

(see : Covariance and Contravariance for Delegates in C#)

Since string is an object then the instance of SampleConvariant can be assigned to the interface ICovariant<out R>.

Contravariance


// Contravariant interface.
interface IContravariant<in A> { }

// Extending contravariant interface.
interface IExtContravariant<in A> : IContravariant<A> { }

// Implementing contravariant interface.
class SampleContravariant<A> : IContravariant<A> { }


The generic type parameter has "in" as a modifier. The "in" declaration can be used if the type parameter is used only as a type of method arguments and not used as a return type of interface methods.

String "is a" Object => true
Object "is a" String => false


IContravariant<Object> contraObj = new SampleContravariant<Object>( );
IContravariant<String> contraStr = new SampleContravariant<String>( );

//contraObj = contraStr; //Cannot implicitly convert IContravariant<string> to IContravariant<object>. An explicit conversion exists (are you missing a cast?)

// You can assign contraObj to contraStr because the IContravariant interface is contravariant.
contraStr = contraObj;


Since we are dealing with arguments (input parameters) and a string is an object you can assign an instance of IContravariant <Object> to IContravariant<String>.

As I am writing this I recognize that the above statement is not clear, even to me. That is because the example code is insufficient. I based the example code on MSDN code. So, let us continue now with a better example.


interface IBetterContravariant<in T>
{
void MyMethod( T t );
}

class BetterContravariant<T> : IBetterContravariant<T>
{
#region IBetterContravariant<T> Members

public void MyMethod( T t )
{
}

#endregion
}


This better example now has a method with a generic input parameter of type <in T> because the interface is contravariant.


IBetterContravariant<Object> betterContraObj = new BetterContravariant<Object>( );
IBetterContravariant<String> betterContraString = new BetterContravariant<String>( );

String s = "text";
Object o = new object( );

betterContraObj.MyMethod( s );
betterContraObj.MyMethod( o );

betterContraString.MyMethod( s );
//betterContraString.MyMethod( o ); //Error: Arugment 1: cannot convert 'object' to 'string'

//betterContraObj = betterContraString; //Cannot implicitly convert... error

// You can assign betterContraObj to betterContraString because IBetterContravariant interface is contravariant
betterContraString = betterContraObj;

betterContraString.MyMethod( s );
//betterContraString.MyMethod( o ); //Error: Arugment 1: cannot convert 'object' to 'string'


Notice that I have instantiated a String and an Object. Before betterContraString is assigned betterContraObj I invoke the "MyMethod" method on each instance of IBetterContravariant twice, once passing a String and then passing an Object.

Notice that betterContraObject.MyMethod accepts a String or an Object and there are no compile time errors. This is because a String "is a" Object.

Notice that betterContraString.MyMethod accepts a String however it does not accept an Object. This is because an Object is NOT a String.

Since betterContraObject accepts Strings (as well as Objects) it can be assigned to betterContraString.

Notice in the example code that after betterContraString is assigned betterContraObject that betterContraString still only accepts Strings as input to MyMethod. This is because betterContrString is still an interface to IBetterContravariant<String>. The interface did not change because of the assignment.

Here is all of the example source code.

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

namespace GenericModifiers
{
interface IDefault<R> { }
class SampleDefault<R> : IDefault<R> { }

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

// Covariant interface.
interface ICovariant<out R> { }

// Extending covariant interface.
interface IExtCovariant<out R> : ICovariant<R> { }

// Implementing covariant interface.
class SampleCovariant<R> : ICovariant<R> { }

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

// Contravariant interface.
interface IContravariant<in A> { }

// Extending contravariant interface.
interface IExtContravariant<in A> : IContravariant<A> { }

// Implementing contravariant interface.
class SampleContravariant<A> : IContravariant<A> { }

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

//Better Contravariant interface example

interface IBetterContravariant<in T>
{
void MyMethod( T t );
}

class BetterContravariant<T> : IBetterContravariant<T>
{
#region IBetterContravariant<T> Members

public void MyMethod( T t )
{
}

#endregion
}

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

class Program
{
static void Main( string[] args )
{
IDefault<Object> dobj = new SampleDefault<Object>( );
IDefault<String> dstr = new SampleDefault<String>( );

//dstr = dobj; //Cannot implicitly convert type IDefault<object> to IDefault<string>. An explicit conversion exists (are you mssing a cast?)
//dobj = dstr; //Cannot implicitly convert type IDefault<string> to IDefault<object>. An explicit conversion exists (are you mssing a cast?)

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

ICovariant<Object> covObj = new SampleCovariant<Object>( );
ICovariant<String> covStr = new SampleCovariant<String>( );

//covStr = covObj; //Cannot implicitly convert type ICovariant<object> to ICovariante<string>. An explicit conversion exists (are you mssing a cast?)

// You can assign covStr to covObj because the ICovariant interface is covariant.
covObj = covStr;

//---------------------------------------------------------
IContravariant<Object> contraObj = new SampleContravariant<Object>( );
IContravariant<String> contraStr = new SampleContravariant<String>( );

//contraObj = contraStr; //Cannot implicitly convert IContravariant<string> to IContravariant<object>. An explicit conversion exists (are you missing a cast?)

// You can assign contraObj to contraStr because the IContravariant interface is contravariant.
contraStr = contraObj;

//---------------------------------------------------------
IBetterContravariant<Object> betterContraObj = new BetterContravariant<Object>( );
IBetterContravariant<String> betterContraString = new BetterContravariant<String>( );

String s = "text";
Object o = new object( );

betterContraObj.MyMethod( s );
betterContraObj.MyMethod( o );

betterContraString.MyMethod( s );
//betterContraString.MyMethod( o ); //Error: Arugment 1: cannot convert 'object' to 'string'

//betterContraObj = betterContraString; //Cannot implicitly convert... error

// You can assign betterContraObj to betterContraString because IBetterContravariant interface is contravariant
betterContraString = betterContraObj;

betterContraString.MyMethod( s );
//betterContraString.MyMethod( o ); //Error: Arugment 1: cannot convert 'object' to 'string'

}
}
}

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

}
}
}