I hope many of you have come across the two buzz words "Co-Variance"
and "Contra-Variance" while browsing through .NET portals. In this article, I
will try to give an introduction to the concepts behind these buzz words.
Covariance and Contravariance are polymorphism extension to
the arrays, delegates and generics. It provides implicit reference conversion for
Arrays, Delegates and Generic parameter types. Covariance preserves the
assignment compatibility and Contravariance opposite the covariance
functionality.
Assignment Compatibility
Actually assignment compatibility is not new to the C# programmer.
It is only the word "Assignment Compatibility" that is new.
Have a look at the following sample code:
String stringObject = "A
String Object";
Object anObject = stringObject;
An Object of a derived class (stringObject) is being assigned
to a variable of a base class (anObject).
Array Covariance
The Array data type has supported Covariance since .NET 1.0. While
working with arrays, you can successfully make the following assignment.
object[] objArray = new String[10];
The code shown above assigns a string array to an object array
variable. That is, a more derived class object's
array (String []) is being assigned to the less derived class object array variable
(object []). This is called covariance in array.
Array Covariance is not safe. Consider the following
statement
objArray[0] = 5;
That statement will not report
any compile time errors. But at runtime, it causes an ArrayTypeMismatchException
exception. It is due to the fact that the objArray variable actually holds a reference
of a string Array.
Delegate Covariance
This type of variance is also called method group variance.
It allows Delegate instances to return more derived class types than what is
specified in the type declaration.
In the following example of covariance, a string returning
function is being assigned to a delegate which is declared to return object
type.
static string GetString() { return ""; }
static void Main()
{
Func<object> delegateObject = GetString;
//String strObject = delegateObject();
}
Contravariant Delegate
The Contravariance Delegates reverse the Covariance functionality. It
allows a method that has parameter types less derived than what is specified in
the delegate.
In the following example a delegate specifies a
parameter type as string. Still we can assign a method that has object as
parameter.
static void SetObject(object objectParameter) { }
static void SetString(string stringParameter) { }
static void Main()
{
Action<string> del2 =
SetObject;
.....
}
What is new in .NET 4.0 about
Delegate Variant?
Implicit conversion
between generic delegates is supported now in .NET 4.0. Previously the compiler
would report an error for the following statement.
static void Main()
{
Func<string> del3 =
GetString;
Func<object> del4 = del3; // Compiler error here until C# 4.0.
}
Variance for generic type
Parameters
In .NET 4.0 Microsoft
introduced implicit type conversion between interface instances that have
different type arguments. This means, an interface instance that has method with
more derived return types than originally specified ( Covariance) or that has methods with less derived
parameter types (Contravariance).
Variant generic
interfaces can be declared using out keywords for generic type parameters.
Conditions for
creating generic covariant parameter for interface types.
ref and out
parameters cannot be declared as a variant. Only reference data type can be
declared as variant, not the value types. For example, IEnumerable<int> cannot be implicitly converted
to IEnumerable<object>, because integers are represented by a value type.
The generic type can be used only as a return type of interface
methods and not used as a type of method arguments.
interface ICovariant<out R>
{
R GetSomething();
//
The following statement generates a compiler error.
//
void SetSometing(R sampleArg);
}
If you have a contravariant generic delegate as a method
parameter, you can use the type as a generic type parameter for the delegate.
interface ICovariant<out R>
{
void DoSomething(Action<R> callback);
}
The Contravariant
generic type parameter can be declared using "in" keyword.
It is also possible to support both
covariance and contravariance in the same interface, but for different type
parameters, as shown in the following code example.
interface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSometings(A sampleArg);
}
More importantly .NET 4.0 introduces
variance support for several existing generic interfaces.
Before .NET 4.0, the
following code causes compilation error, but now you
can use strings instead of objects, as shown in the example, because the IEnumerable(Of T) interface
is covariant.
IEnumerable<String>
strings = new
List<String>();
IEnumerable<Object>
objects = strings;