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'

}
}
}

No comments: