Covariance and Contravariance in Generics
奇怪的是微软有两篇文章来说明协变和逆变
文章1
- Docs
- .NET
- C# guide
- Programming guide
- Programming concepts
- Covariance and contravariance
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/
文章2 第二篇文章写得更清晰,是C#和VB双版本的
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types. When you are referring to a type system, covariance, contravariance, and invariance have the following definitions. The examples assume a base class named Base
and a derived class named Derived
.
-
Covariance
Enables you to use a more derived type than originally specified.
You can assign an instance of
IEnumerable<Derived>
(IEnumerable(Of Derived)
in Visual Basic) to a variable of typeIEnumerable<Base>
. -
Contravariance
Enables you to use a more generic (less derived) type than originally specified.
You can assign an instance of
Action<Base>
(Action(Of Base)
in Visual Basic) to a variable of typeAction<Derived>
. -
Invariance
Means that you can use only the type originally specified; so an invariant generic type parameter is neither covariant nor contravariant.
You cannot assign an instance of
List<Base>
(List(Of Base)
in Visual Basic) to a variable of typeList<Derived>
or vice versa.
Covariant type parameters enable you to make assignments that look much like ordinary Polymorphism, as shown in the following code.
IEnumerable<Derived> d = new List<Derived>(); IEnumerable<Base> b = d;
The List<T> class implements the IEnumerable<T> interface, so List<Derived>
(List(Of Derived)
in Visual Basic) implements IEnumerable<Derived>
. The covariant type parameter does the rest.
Contravariance, on the other hand, seems counterintuitive. The following example creates a delegate of type Action<Base>
(Action(Of Base)
in Visual Basic), and then assigns that delegate to a variable of type Action<Derived>
.
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); }; Action<Derived> d = b; d(new Derived());
This seems backward, but it is type-safe code that compiles and runs. The lambda expression matches the delegate it is assigned to, so it defines a method that takes one parameter of type Base
and that has no return value. The resulting delegate can be assigned to a variable of type Action<Derived>
because the type parameter T
of the Action<T> delegate is contravariant. The code is type-safe because T
specifies a parameter type. When the delegate of type Action<Base>
is invoked as if it were a delegate of type Action<Derived>
, its argument must be of type Derived
. This argument can always be passed safely to the underlying method, because the method's parameter is of type Base
.
In general, a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types. For an interface, covariant type parameters can be used as the return types of the interface's methods, and contravariant type parameters can be used as the parameter types of the interface's methods.
Covariance and contravariance are collectively referred to as variance. A generic type parameter that is not marked covariant or contravariant is referred to as invariant. A brief summary of facts about variance in the common language runtime:
-
In the .NET Framework 4, variant type parameters are restricted to generic interface and generic delegate types.
-
A generic interface or generic delegate type can have both covariant and contravariant type parameters.
-
Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
-
Variance does not apply to delegate combination. That is, given two delegates of types
Action<Derived>
andAction<Base>
(Action(Of Derived)
andAction(Of Base)
in Visual Basic), you cannot combine the second delegate with the first although the result would be type safe. Variance allows the second delegate to be assigned to a variable of typeAction<Derived>
, but delegates can combine only if their types match exactly.
public void Test() { //https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance //Covariance, Enables you to use a more derived type than originally specified. 用来做返回值 //public interface IEnumerable<out T> : IEnumerable IEnumerable<Derived> derivedList = new List<Derived>(); IEnumerable<Base> baseList = derivedList; //Contravariance, Enables you to use a more generic (less derived) type than originally specified. 用来做函数参数 //public delegate void Action<in T>(T obj) Action<Base> actionBase = ActionMethod; Action<Derived> actionDerived = actionBase; actionDerived(new Derived()); //Invariance //public class List<T> List<Base> baseList2 = new List<Base>(); List<Derived> derivedList2 = new List<Derived>(); //The following code is not allowed //baseList2 = derivedList2; //derivedList2 = baseList2; } public void ActionMethod(Base baseInstance) { Console.WriteLine(baseInstance.GetType().Name); }
关于out的可以用来处理函数参数和返回值,Covariance enables you to use a more derived type than that specified by the generic parameter.
private void ValidateEmailDomains(List<string> emailDomains) 可以被优化成下面的结构,参数类型使用IReadOnlyCollection<T>
private void ValidateEmailDomains(IReadOnlyCollection<string> emailDomains)
然后传参数的时候,参数的实例可以是List<string>,Queue<string> 参数变成了更具体的类型,可以说是把子类型的实例,传递给父类型的参数