协变和逆变都是术语,前者指可以使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指可以使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。 在引用类型系统时,协变、逆变和不变性具备以下定义。 这些示例假定一个名为 Base
的基类和一个名为 Derived
的派生类。html
Covariance
编程
使你可以使用比原始指定的类型派生程度更大的类型。api
你能够向 IEnumerable<Derived>
类型的变量分配IEnumerable(Of Derived)
(在 Visual Basic 中为 IEnumerable<Base>
)的实例。安全
Contravariance
app
使你可以使用比原始指定的类型更泛型(派生程度更小)的类型。ide
你能够向 Action<Base>
类型的变量分配Action(Of Base)
(在 Visual Basic 中为 Action<Derived>
)的实例。函数
Invariance
ui
这意味着,你只能使用原始指定的类型;固定泛型类型参数既不是协变类型,也不是逆变类型。spa
你没法向 List<Base>
类型的变量分配 List(Of Base)
(在 Visual Basic 中为 List<Derived>
)的实例,反之亦然。code
利用协变类型参数,你能够执行很是相似于普通的多态性的分配,如如下代码中所示。
IEnumerable<Derived> d = new List<Derived>(); IEnumerable<Base> b = d; // 协变
List<T> 类实现 IEnumerable<T> 接口,所以 List<Derived>
(在 Visual Basic 中为List(Of Derived)
)实现 IEnumerable<Derived>
。 协变类型参数将执行其他的工做。
相反,逆变看起来却不够直观。 下面的示例建立类型 Action<Base>
(在 Visual Basic 中为Action(Of Base)
)的委托,而后将此委托分配给类型 Action<Derived>
的变量。
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); }; Action<Derived> d = b; // 逆变 d(new Derived());
此示例看起来是倒退了,但它是可编译和运行的类型安全代码。 因为 lambda 表达式与其自身所分配到的委托相匹配,所以它会定义一个方法,此方法采用一个类型 Base
的参数且没有返回值。 能够将结果委托分配给类型类型 Action<Derived>
的变量,由于 T
委托的类型参数 Action<T> 是逆变类型参数。 因为 T
指定了一个参数类型,所以该代码是类型安全代码。 在调用类型 Action<Base>
的委托(就像它是类型 Action<Derived>
的委托同样)时,其参数必须属于类型 Derived
。 始终能够将此实参安全地传递给基础方法,由于该方法的形参属于类型 Base
。
一般,协变类型参数可用做委托的返回类型,而逆变类型参数可用做参数类型。 对于接口,协变类型参数可用做接口的方法的返回类型,而逆变类型参数可用做接口的方法的参数类型。
协变和逆变统称为“变体” 。 未标记为协变或逆变的泛型类型参数称为“固定参数” 。 有关公共语言运行时中变体的事项的简短摘要:
在 .NET Framework 4 中,Variant 类型参数仅限于泛型接口和泛型委托类型。
泛型接口或泛型委托类型能够同时具备协变和逆变类型参数。
变体仅适用于引用类型;若是为 Variant 类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。
变体不适用于委托组合。 也就是说,在给定类型 Action<Derived>
和 Action<Base>
(在 Visual Basic 中为Action(Of Derived)
和 Action(Of Base)
)的两个委托的状况下,没法将第二个委托与第一个委托结合起来,尽管结果将是类型安全的。 变体容许将第二个委托分配给类型 Action<Derived>
的变量,但只能在这两个委托的类型彻底匹配的状况下对它们进行组合。
从 .NET Framework 4 开始,某些泛型接口具备协变类型参数;例如:IEnumerable<T>、IEnumerator<T>、IQueryable<T> 和 IGrouping<TKey,TElement>。 因为这些接口的全部类型参数都是协变类型参数,所以这些类型参数只用于成员的返回类型。
下面的示例阐释了协变类型参数。 此示例定义了两个类型: Base
具备一个名为 PrintBases
的静态方法,该方法采用 IEnumerable<Base>
(在 Visual Basic 中为IEnumerable(Of Base)
)并输出元素。 Derived
继承自 Base
。 此示例建立一个空 List<Derived>
(在 Visual Basic 中为List(Of Derived)
),而且说明能够将该类型传递给 PrintBases
且在不进行强制转换的状况下将该类型分配给类型 IEnumerable<Base>
的变量。 List<T> 实现 IEnumerable<T>,它具备一个协变类型参数。 协变类型参数是可以使用 IEnumerable<Derived>
的实例而非 IEnumerable<Base>
的缘由。
using System; using System.Collections.Generic; class Base { public static void PrintBases(IEnumerable<Base> bases) { foreach(Base b in bases) { Console.WriteLine(b); } } } class Derived : Base { public static void Main() { List<Derived> dlist = new List<Derived>(); Derived.PrintBases(dlist); IEnumerable<Base> bIEnum = dlist; } }
从 .NET Framework 4 开始,某些泛型接口具备逆变类型参数;例如:IComparer<T>、IComparable<T> 和 IEqualityComparer<T>。 因为这些接口只具备逆变类型参数,所以这些类型参数只用做接口成员中的参数类型。
下面的示例阐释了逆变类型参数。 该示例定义具备MustInherit
属性的抽象(在 Visual Basic 中为 Shape
) Area
类。 该示例还定义一个实现 ShapeAreaComparer
(在 Visual Basic 中为 IComparer<Shape>
)的IComparer(Of Shape)
类。 IComparer<T>.Compare 方法的实现基于 Area
属性的值,因此 ShapeAreaComparer
可用于按区域对 Shape
对象排序。
Circle
类继承 Shape
并重写 Area
。 该示例建立 SortedSet<T> 对象的 Circle
,使用采用 IComparer<Circle>
(在 Visual Basic 中为IComparer(Of Circle)
)的构造函数。 可是,该对象不传递 IComparer<Circle>
,而是传递一个用于实现 ShapeAreaComparer
的 IComparer<Shape>
对象。 当代码须要派生程度较大的类型的比较器 (Shape
) 时,该示例能够传递派生程度较小的类型的比较器 (Circle
),由于 IComparer<T> 泛型接口的类型参数是逆变参数。
向 Circle
中添加新 SortedSet<Circle>
对象时,每次将新元素与现有元素进行比较时,都会调用 IComparer<Shape>.Compare
对象的IComparer(Of Shape).Compare
方法(在 Visual Basic 中为 ShapeAreaComparer
方法)。 方法 (Shape
) 的参数类型比被传递的类型 (Circle
) 的派生程度小,因此调用是类型安全的。 逆变使 ShapeAreaComparer
能够对派生自 Shape
的任意单个类型的集合以及混合类型的集合排序。
using System; using System.Collections.Generic; abstract class Shape { public virtual double Area { get { return 0; }} } class Circle : Shape { private double r; public Circle(double radius) { r = radius; } public double Radius { get { return r; }} public override double Area { get { return Math.PI * r * r; }} } class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape> { int IComparer<Shape>.Compare(Shape a, Shape b) { if (a == null) return b == null ? 0 : -1; return b == null ? 1 : a.Area.CompareTo(b.Area); } } class Program { static void Main() { // 能够传递实现IComparer<shape>的shapeareComparer,
// 即便sortedset<circle>的构造函数须要IComparer<circle>,
// 由于IComparer<t>的类型参数T是反向的。 SortedSet<Circle> circlesByArea = new SortedSet<Circle>(new ShapeAreaComparer()) { new Circle(7.2),
new Circle(100),
null,
new Circle(.01)
}; foreach (Circle c in circlesByArea) { Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area); } } } /* 输出结果: null Circle with area 0.000314159265358979 Circle with area 162.860163162095 Circle with area 31415.9265358979 */
Func
泛型委托(如 Func<T,TResult>)具备协变返回类型和逆变参数类型。 Action
泛型委托(如 Action<T1,T2>)具备逆变参数类型。 这意味着,能够将委托指派给具备派生程度较高的参数类型和(对于 Func
泛型委托)派生程度较低的返回类型的变量。
Func 泛型委托的最后一个泛型类型参数指定委托签名中返回值的类型。 该参数是协变的(out 关键字),而其余泛型类型参数是逆变的(in 关键字)。
Base
的类、一个名为 Derived
的类(此类继承 Base
)和另外一个具备名为 static
的Shared
方法(在 Visual Basic 中为 MyMethod
)的类。 该方法采用 Base
的实例,并返回 Derived
的实例。 (若是参数是 Derived
的实例,则 MyMethod
将返回该实例;若是参数是 Base
的实例,则 MyMethod
将返回 Derived
的新实例。)在 Main()
中,该示例建立一个表示 Func<Base, Derived>
的 Func(Of Base, Derived)
(在 Visual Basic 中为 MyMethod
)的实例,并将此实例存储在变量 f1
中。
1 public class Base {} 2 public class Derived : Base {} 3 4 public class Program 5 { 6 public static Derived MyMethod(Base b) 7 { 8 return b as Derived ?? new Derived(); 9 } 10 11 static void Main() 12 { 13 Func<Base, Derived> f1 = MyMethod; 14 15 /* 能够将委托分配给类型 Func<Base, Base> (在 Visual Basic 中为Func(Of Base, Base) )的变量,由于返回类型是协变的。*/ 16 // 协变返回类型 17 Func<Base, Base> f2 = f1; 18 Base b2 = f2(new Base()); 19 20 /* 能够将委托分配给类型 Func<Derived, Derived> (在 Visual Basic 中为Func(Of Derived, Derived) )的变量,由于参数类型是逆变的。*/ 21 // 逆变参数类型 22 Func<Derived, Derived> f3 = f1; 23 Derived d3 = f3(new Derived()); 24 25 /* 能够将委托分配给类型 Func<Derived, Base> (在 Visual Basic 中为Func(Of Derived, Base) )的变量,从而将逆变参数类型和协变返回类型的做用结合起来。*/ 26 // 协变返回类型和逆变参数类型。 27 Func<Derived, Base> f4 = f1; 28 Base b4 = f4(new Derived()); 29 } 30 } 31
在上面的代码中, MyMethod
的签名与所构造的泛型委托 Func<Base, Derived>
(在 Visual Basic 中为Func(Of Base, Derived)
)的签名彻底匹配。 此示例说明,只要全部委托类型都是从泛型委托类型 Func<T,TResult>构造的,就能够将此泛型委托存储在具备派生程度更大的参数类型和派生程度更小的返回类型的变量或方法参数中。
这一点很是重要。 泛型委托的类型参数中的协方差和逆变的效果相似于普通委托绑定中的协方差和逆变的效果(请参阅委托中的差别 (C#) 和委托中的差别 (Visual Basic))。 可是,委托绑定中的变化适用于全部委托类型,而不只仅适用于具备 Variant 类型参数的泛型委托类型。 此外,经过委托绑定中的变化,能够将方法绑定到具备限制较多的参数类型和限制较少的返回类型的任何委托,而对于泛型委托的指派,只有在委托类型是基于同一个泛型类型定义构造的时才能够进行。
下面的示例演示委托绑定中的变化和泛型类型参数中的变化的组合效果。 该示例定义了一个类型层次结构,其中包含三个按派生程度从低到高排列的类型,即Type1
的派生程度最低,Type3
的派生程度最高。 普通委托绑定中的变化用于将参数类型为 Type1
、返回类型为 Type3
的方法绑定到参数类型为 Type2
、返回类型为 Type2
的泛型委托。 而后,使用泛型类型参数的协变和逆变,将获得的泛型委托指派给另外一个变量,此变量的泛型委托类型的参数类型为 Type3
,返回类型为 Type1
。 第二个指派要求变量类型和委托类型是基于同一个泛型类型定义(在本例中为 Func<T,TResult>)构造的。
using System; public class Type1 {} public class Type2 : Type1 {} public class Type3 : Type2 {} public class Program { public static Type3 MyMethod(Type1 t) { return t as Type3 ?? new Type3(); } static void Main() { Func<Type2, Type2> f1 = MyMethod; // 协变返回类型和逆变参数类型 Func<Type3, Type1> f2 = f1; Type1 t1 = f2(new Type3()); } }
从 .NET Framework 2.0 版开始,公共语言运行时支持泛型类型参数上的变化批注。 在 .NET Framework 4 以前,定义包含这些批注的泛型类的惟一方法就是利用 Ilasm.exe(IL 汇编程序) 编译该类或在动态程序集中发出该类,从而使用 Microsoft 中间语言 (MSIL)。
若是接口的方法具备泛型委托类型的参数,则接口类型的协变类型参数可用于指定委托类型的逆变类型参数。
逆变类型参数用 in
关键字(在 Visual Basic 中为In
关键字,在 -
MSIL 汇编程序 中为)标记。 能够将逆变类型参数用做属于接口的方法的参数类型,或用做委托的参数类型。 也能够将逆变类型参数用做接口方法的泛型类型约束。
只有接口类型和委托类型才能具备 Variant 类型参数。 接口或委托类型能够同时具备协变和逆变类型参数。
Visual Basic 和 C# 不容许违反协变和逆变类型参数的使用规则,也不容许将协变和逆变批注添加到接口和委托类型以外的类型参数中。 MSIL 汇编程序 不执行此类检查,但若是你尝试加载违反规则的类型,则会引起 TypeLoadException 。
有关信息和示例代码,请参阅泛型接口中的差别 (C#) 和泛型接口中的差别 (Visual Basic)。
在 .NET Framework 4 中,下面的接口和委托类型具备协变和/或逆变类型参数。