.Net Contravariance & Covariance notes
Variant == Contravariant or Covariant.
Invariant == NOT (Contravariant or Covariant)
All next members of an interface will result in compilation errors.
interface IVariant<out TReturn, in TArgument>
{
// CS1961 Invalid variance:
// The type parameter 'TReturn' must be contravariantly valid on 'IVariant<TReturn, TArgument>.SetA(TReturn)'.
// 'TReturn' is covariant.
void SetA(TReturn smpleArg);
// CS1961 Invalid variance:
// The type parameter 'TArgument' must be covariantly valid on 'IVariant<TReturn, TArgument>.GetA()'.
// 'TArgument' is contravariant.
TArgument GetA();
// CS1961 Invalid variance:
// The type parameter 'TArgument' must be covariantly valid on 'IVariant<TReturn, TArgument>.GetASetR(TReturn)'.
// 'TArgument' is contravariant.
// CS1961 Invalid variance:
// The type parameter 'TReturn' must be contravariantly valid on 'IVariant<TReturn, TArgument>.GetASetR(TReturn)'.
// 'TReturn' is covariant.
TArgument GetASetR(TReturn sampleArg);
}
So, there are rules for the "in" and "out".
Contravariance is the "in" parameter.
Contravariance permits a method to have argument types that are less derived than that specified by the generic parameter of the interface.
The contravariant type can be used only as a type of method arguments and not as a return type of interface methods.
Only the contravariant type can be used for generic constraints. Not invariant or covariant types!
Covariance is the "out" parameter.
Covariance permits a method to have a more derived return type than that defined by the generic type parameter of the interface.
The type is used only as a return type of interface methods and not used as a type of method arguments.
There is one exception to this rule. 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);
// Compiler error: void DoSomething(R param);
}
Generic Interfaces
Classes that implement variant interfaces are invariant.
IEnumerable<Object> listObjects = new List<String>();
// Compiler error: List<Object> list = new List<String>();
The compiler does not infer the variance from the interface that is being extended.
If a generic type parameter T is declared covariant in one interface, you cannot declare it contravariant in an extending interface, or vice versa.
interface ICovariant<out T> { }
// Compiler error: interface ICoContraVariant<in T> : ICovariant<T> { }
Ambiguity. Can lead to subtle bugs in your code.
The compiler does not produce an error
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
Array Covariance Collision
Array Covariance turns compile-time exceptions into run-time exceptions.
In .NET all types derived (inherited) from the Object type.
For example String is derived from Object.
But an array of strings string[] is not derived from an array of objects object[].
Nevertheless .NET implements a feature that known as Array Covariance.
That means that we can implicitly cast a derived[] array to a base[].
string[] stringArray = {"a"};
object[] objectArray = stringArray;
// ArrayTypeMismatchException here.
objectArray[0] = 1;
Delegates
For an assigning of method (lambda expression) to a delegate there is an implicit conversion with the Contravariance & Covariance.
It has no trouble.
Framework 4. The point of the variance is the implicit variance conversion between delegates.
Variance is supported for reference types only.
For value types variance is ignored.
You should not combine variant delegates
run-time exception
Properties
You get the compiler error because you have a property getter (get) and a setter (set).
Invariant == NOT (Contravariant or Covariant)
All next members of an interface will result in compilation errors.
interface IVariant<out TReturn, in TArgument>
{
// CS1961 Invalid variance:
// The type parameter 'TReturn' must be contravariantly valid on 'IVariant<TReturn, TArgument>.SetA(TReturn)'.
// 'TReturn' is covariant.
void SetA(TReturn smpleArg);
// CS1961 Invalid variance:
// The type parameter 'TArgument' must be covariantly valid on 'IVariant<TReturn, TArgument>.GetA()'.
// 'TArgument' is contravariant.
TArgument GetA();
// CS1961 Invalid variance:
// The type parameter 'TArgument' must be covariantly valid on 'IVariant<TReturn, TArgument>.GetASetR(TReturn)'.
// 'TArgument' is contravariant.
// CS1961 Invalid variance:
// The type parameter 'TReturn' must be contravariantly valid on 'IVariant<TReturn, TArgument>.GetASetR(TReturn)'.
// 'TReturn' is covariant.
TArgument GetASetR(TReturn sampleArg);
}
So, there are rules for the "in" and "out".
Contravariance is the "in" parameter.
Contravariance permits a method to have argument types that are less derived than that specified by the generic parameter of the interface.
The contravariant type can be used only as a type of method arguments and not as a return type of interface methods.
Only the contravariant type can be used for generic constraints. Not invariant or covariant types!
Covariance is the "out" parameter.
Covariance permits a method to have a more derived return type than that defined by the generic type parameter of the interface.
The type is used only as a return type of interface methods and not used as a type of method arguments.
There is one exception to this rule. 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);
// Compiler error: void DoSomething(R param);
}
Generic Interfaces
Classes that implement variant interfaces are invariant.
IEnumerable<Object> listObjects = new List<String>();
// Compiler error: List<Object> list = new List<String>();
The compiler does not infer the variance from the interface that is being extended.
If a generic type parameter T is declared covariant in one interface, you cannot declare it contravariant in an extending interface, or vice versa.
interface ICovariant<out T> { }
// Compiler error: interface ICoContraVariant<in T> : ICovariant<T> { }
Ambiguity. Can lead to subtle bugs in your code.
The compiler does not produce an error
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
Array Covariance Collision
Array Covariance turns compile-time exceptions into run-time exceptions.
In .NET all types derived (inherited) from the Object type.
For example String is derived from Object.
But an array of strings string[] is not derived from an array of objects object[].
Nevertheless .NET implements a feature that known as Array Covariance.
That means that we can implicitly cast a derived[] array to a base[].
string[] stringArray = {"a"};
object[] objectArray = stringArray;
// ArrayTypeMismatchException here.
objectArray[0] = 1;
Delegates
For an assigning of method (lambda expression) to a delegate there is an implicit conversion with the Contravariance & Covariance.
It has no trouble.
Framework 4. The point of the variance is the implicit variance conversion between delegates.
Variance is supported for reference types only.
For value types variance is ignored.
You should not combine variant delegates
run-time exception
Properties
You get the compiler error because you have a property getter (get) and a setter (set).
Comments
Post a Comment