1. Types of variability: covariance and contravariance
Variability is the use of one object as another object in a type-safe manner. If you cannot replace one type with another, then this type is called: invariants . Covariance and contravariance are two opposing concepts:
- If a returned type can be replaced by its derived type, then this type is the one that supports covariant
- If a parameter type can be replaced by its base class, then this type is supported for contravariance .
2. C # 4.0 support for generic variability
Before C # 4.0, all generic types were invariants -that is, replacing one generic type with another, even if they had an inheritance relationship, in short, the generics before C # 4.0 did not support covariance and contravariance.
C # 4.0 out
in
uses generics in a covariant and contravariant manner, with two keywords: and.
Let's take a look at the code that takes advantage of the covariant type parameter:
public class BaseClass{ //...}public class DerivedClass : BaseClass{ //...}
Below we use covariant type parameters to perform allocations similar to ordinary polymorphism:
IEnumerable<DerivedClass> d = new List<DerivedClass>();IEnumerable<BaseClass> b = d;
In the above instance, before C # 4.0, it was not compiled properly, except for a cast of the subclass collection when assigned to the base class collection, but still throws a type cast exception at run time.
Let's look at an example code for contravariance:
b = (target) => { Console.WriteLine(target.GetType().Name); };Action<DerivedClass> d = b;d(new DerivedClass());
In the example above, the Action<BaseClass>
delegate of our type is assigned to a variable of type Action<DerivedClass>
, and according to the definition of the contravariant we can know that the Action<T>
type is supported for contravariance.
Why IEnumerable<T>
and what Action<T>
types of contravariance and covariance can be supported separately? Let's look at the definitions of these two types in. NET:
//IEnumerable<T> 接口的定义(支持协变)public interface IEnumerable<out T> : IEnumerable//Action<T> 委托的定义(支持逆变)public delegate void Action<in T>(T obj);
To ensure type safety, the C # compiler out
in
adds some restrictions to generic parameters that use the and keywords:
- Type parameters that support covariant (
out
) can only be used in the output location: function return value, property's get accessor, and some location of the delegate parameter
- Type parameters that support contravariant (
in
) can only be used in the input location: The method parameter or some position of the delegate parameter.
3. Limitations of generic variability in C #
1. Variability of type parameters that do not support classes
Only interfaces and delegates can have variable type parameters. in
and out
modifiers can only be used to decorate generic interfaces and generic delegates.
2. Immutability only supports reference conversions
Immutability can only be used for reference types, prohibit any value types and user-defined conversions, as the following conversions are not valid:
IEnumerable<int>
Convert to IEnumerable<object>
--boxing conversion
IEnumerable<short>
Convert to IEnumerable<int>
--value type conversion
IEnumerable<string>
Convert to IEnumerable<XName>
--user-defined conversions
3. Type parameters are used out or ref will prohibit variability
For a generic type parameter, if you want to pass the argument of that type to out
a ref
method that uses or a keyword, you do not allow variability, such as:
void someDelegate<in T>(ref T t)
This code compiler will make an error.
4. The variability must be explicitly specified
From the implementation of the compiler can fully determine which generic parameters can be contravariant and covariant, but actually did not do so, because the C # development team believes that:
Variability must be explicitly specified by the developer, as it will encourage developers to think about the consequences of their actions and to consider whether their designs are reasonable.
5. Notice disruptive modifications
There is a risk of breaking the current code when modifying the variability of an existing code interface. For example, if you rely on the result of an IS or as operator that does not allow variability, the behavior of the code will be different when running at. NET 4 o'clock. Similarly, in some cases, overload resolution chooses different methods because there are more options available. Therefore, sufficient unit testing and defensive measures should be done to introduce variability into existing code.
6. Multicast delegation and variability can not be mixed
The following code can be compiled, but throws an exception at run time ArgumentException
:
stringFunc = () => "";Func<object> objectFunc = () => new object();Func<object> combined = objectFunc + stringFunc;
This is because the method that is responsible for linking multiple delegates requires that the Delegate.Combine
parameters must be of the same type. The above example can be modified to the correct code as follows:
Func<string> stringFunc = () => "";Func<object> defensiveCopy = new Func<object>(stringFunc);Func<object> objectFunc = () => new object();Func<object> combined = objectFunc + defensiveCopy;
Reference & Extended Reading
Covariance and Contravariance
Covariance and Contravariance in generics
Covariance and Contravariance in delegates
"In-depth understanding of C #": 13.3 generic variability of interfaces and delegates
"Effective C #": Entry 29: Support for generic covariance and contravariance
CLR via C #: 12.5 contravariant and covariant generic type arguments for delegates and interfaces
Category: C #
Covariance and contravariance of generics