In Contravariance, what does it mean for a reference's assignment compatibility to be reversed?
Covariance and Contravariance (C#)
// 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 the following method is in the class: // 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;
Moreover, when attempting to cast from a less-derived type to a more-derived one, an InvalidCastException
is thrown. Since user-defined conversions aren't allowed to or from a base class, I don't see how contravariance of method groups even work - wouldn't/shouldn't invoking such a method throw an InvalidCastException
as well?
Assignment compatibility is reversed means that you can assign a less derived type to a more derived type.
In the example the contravariance is shown with the Action
class. Action<object>
can be assigned to Action<string>
.
This code is valid:
public void Test(object o)
{
Console.WriteLine(o.GetType().ToString());
}
Action<string> foo;
foo = Test;
foo.Invoke("bar");
In this example we can see that the string "bar" is implicitly cast to the object type when it's passed as a parameter to the Test
method. There is no invalid cast or user-defined conversion involved.
Action
is not covariant, so Action<string>
can't be assigned to Action<object>
, trying to do so would throw an exception.
Generic types are contravariant when the generic type is an input parameter and covariant with output parameters.
So the Func
class is covariant with it's first type and contravariant with it's second (and third, ...):
Func<string, object>
can be assigned to Func<string, string>
Func<string, string>
can be assigned to Func<object, string>
Func<string, object>
can be assigned to Func<object, string>
Func<object, string>
can't be assigned to Func<string, string>
. To declare a contravariant generic type in C# we can declare a contravariant interface with the in
keyword. In doing so we are restricted to use the generic type as an input parameter:
interface IMyGenericType<in T>
{
object Test(T input); //allowed
//T GetValue(); //not allowed in a contravariant interface
}
With this type I can assign IMyGenericType<object>
to IMyGenericType<string>
.