Showing posts with label Covariance. Show all posts
Showing posts with label Covariance. Show all posts

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'

}
}
}