Thursday, 27 March 2014

Covariance and Contravariance

Covariance and Contravariance

In this post I’ll try to answer the most common questions I find on forums and in documentation feedback about C# covariance and contravariance. It’s a big topic for a single blog post, so expect to see a lot of “more information” links.
Special thanks to Eric Lippert and Chris Burrows for reviewing and providing helpful comments.

Example
Class String:Object
  • Covariance => Object=string;
  • Contravariance=> string=Object;
Covariance interface and delegate must have 'In' keyword declaration for input parameter. Contravariance interface and delegate must have 'Out' keyword declaration for output parameter.


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;


 What are covariance and contravariance?
In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments.Covariance preserves assignment compatibility and contravariance reverses it.
The following code demonstrates the difference between assignment compatibility, covariance, and contravariance.
// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;

// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;

// Contravariance.           
// Assume that I have this method:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
In C#, variance is supported in the following scenarios:
  1. Covariance in arrays (since C# 1.0)
  2. Covariance and contravariance in delegates, also known as “method group variance” (since C# 2.0)
  3. Variance for generic type parameters in interfaces and delegates (since C# 4.0)

What is array covariance?

Arrays are covariant since C# 1.0. You can always do the following:
object[] obj = new String[10];
In the above code, I assigned an array of strings to an array of objects. So I used a more derived type than that originally specified, which is covariance.
Covariance in arrays is considered “not safe,” because you can also do this:
obj[0] = 5;
This code compiles, but it throws an exception at run time because obj is in fact an array of strings and cannot contain integers.

What is delegate, or method group, variance?

This feature was added in C# 2.0. When you instantiate a delegate, you can assign it a method that has a more derived return type than that specified in the delegate (covariance). You can also assign a method that has parameter types less derived than those in the delegate (contravariance).
Here’s a quick code example illustrating the feature and some of its limitations.
static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Main()
{
    // Covariance. A delegate specifies a return type as object,
    // but I can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string,
    // but I can assign a method that takes an object.
    Action<string> del2 = SetObject;

    // But implicit conversion between generic delegates is not supported until C# 4.0.
    Func<string> del3 = GetString;
    Func<object> del4 = del3; // Compiler error here until C# 4.0.
}
By the way, this feature works for all delegates, both generic and non-generic, not just for Func and Action delegates.
For more information and examples, see Covariance and Contravariance in Delegates on MSDN and Eric Lippert’s post Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance.

What is variance for generic type parameters?

This is a new feature in C# 4.0. Now, when creating a generic interface, you can specify whether there is an implicit conversion between interface instances that have different type arguments. For example, you can use an interface instance that has methods with more derived return types than originally specified (covariance) or that has methods with less derived parameter types (contravariance). The same rules are applied to generic delegates.
While you can create variant interfaces and delegates yourself, this is not the main purpose for this feature. What is more important is that a set of interfaces and delegates in .NET Framework 4 have been updated to become variant.
Here’s the list of updated interfaces:
And the list of updated delegates:
The most frequent scenario for most users is expected to be something like this one:
IEnumerable<Object> objects = new List<String>();
While this code doesn’t look that impressive, it allows you to reuse a lot of methods that accept IEnumerable objects.
class Program
{
    // The method has a parameter of the IEnumerable<Person> type.
    public static void PrintFullName(IEnumerable<Person> persons)
    {
        // The method iterates through a sequence and prints some info.
    }

    public static void Main()
    {
        List<Employee> employees = new List<Employee>();

        // I can pass List<Employee>, which is in fact IEnumerable<Employee>,
        // although the method expects IEnumerable<Person>.
        PrintFullName(employees);
    }
}
A couple of important rules to remember:
  • This feature works only for generic interfaces and delegates. If you implement a variant generic interface, the implementing class is still invariant. Classes and structs do not support variance in C# 4.0.
    So the following doesn’t compile:
    // List<T> implements the covariant interface
    // IEnumerable<out T>. But classes are invariant.
    List<Person> list = new List<Employee>(); // Compiler error here.
  • Variance is supported only if a type parameter is a reference type. Variance is not supported for value types.
    The following doesn’t compile either:
    // int is a value type, so the code doesn't compile.
    IEnumerable<Object> objects = new List<int>(); // Compiler error here.
     

No comments:

Post a Comment