【已更新最新开发文章,点击查看详细】

这些示例假定一个名为 Base 的基类和一个名为 Derived的派生类。

  • Covariance

    使你能够使用比原始指定的类型派生程度更大的类型。

    你可以向 IEnumerable<Derived> 类型的变量分配IEnumerable(Of Derived) (在 Visual Basic 中为 IEnumerable<Base>)的实例。

  • Contravariance

    使你能够使用比原始指定的类型更泛型(派生程度更小)的类型。

    你可以向 Action<Base> 类型的变量分配Action(Of Base) (在 Visual Basic 中为 Action<Derived>)的实例。

  • Invariance

    这意味着,你只能使用原始指定的类型;固定泛型类型参数既不是协变类型,也不是逆变类型。

    你无法向 List<Base> 类型的变量分配 List(Of Base)(在 Visual Basic 中为 List<Derived>)的实例,反之亦然。

多态性的分配,如以下代码中所示。

IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d; // 协变

协变类型参数将执行其余的工作。

下面的示例创建类型 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());

始终可以将此实参安全地传递给基础方法,因为该方法的形参属于类型 Base

对于接口,协变类型参数可用作接口的方法的返回类型,而逆变类型参数可用作接口的方法的参数类型。

有关公共语言运行时中变体的事项的简短摘要:

  • 在 .NET Framework 4 中,Variant 类型参数仅限于泛型接口和泛型委托类型。

  • 泛型接口或泛型委托类型可以同时具有协变和逆变类型参数。

  • 变体仅适用于引用类型;如果为 Variant 类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。

  • 变体允许将第二个委托分配给类型 Action<Derived>的变量,但只能在这两个委托的类型完全匹配的情况下对它们进行组合。

具有协变类型参数的泛型接口

由于这些接口的所有类型参数都是协变类型参数,因此这些类型参数只用于成员的返回类型。

协变类型参数是可使用 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;
    }
}
具有逆变泛型类型参数的泛型接口

由于这些接口只具有逆变类型参数,因此这些类型参数只用作接口成员中的参数类型。

IComparer<T>.Compare 方法的实现基于 Area 属性的值,所以 ShapeAreaComparer 可用于按区域对 Shape 对象排序。

IComparer<T> 泛型接口的类型参数是逆变参数。

逆变使 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 */
具有 Variant 类型参数的泛型委托
这意味着,可以将委托指派给具有派生程度较高的参数类型和(对于 Func 泛型委托)派生程度较低的返回类型的变量。

Func 泛型委托的最后一个泛型类型参数指定委托签名中返回值的类型。 该参数是协变的(out 关键字),而其他泛型类型参数是逆变的(in 关键字)。

下面的代码阐释这一点。 (如果参数是 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         

泛型委托和非泛型委托中的变体

Func<T,TResult>构造的,就可以将此泛型委托存储在具有派生程度更大的参数类型和派生程度更小的返回类型的变量或方法参数中。

此外,通过委托绑定中的变化,可以将方法绑定到具有限制较多的参数类型和限制较少的返回类型的任何委托,而对于泛型委托的指派,只有在委托类型是基于同一个泛型类型定义构造的时才可以进行。

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());
    }
}
定义 Variant 泛型接口和委托
从 .NET Framework 4 开始,Visual Basic 和 C# 提供了一些关键字,利用这些关键字,可以将接口和委托的泛型类型参数标记为协变或逆变。

从 .NET Framework 2.0 版开始,公共语言运行时支持泛型类型参数上的变化批注。 在 .NET Framework 4 之前,定义包含这些批注的泛型类的唯一方法就是利用 Ilasm.exe(IL 汇编程序) 编译该类或在动态程序集中发出该类,从而使用 Microsoft 中间语言 (MSIL)。

协变类型参数用 out 关键字(在 Visual Basic 中为Out 关键字,在 + MSIL 汇编程序 中为)标记。 可以将协变类型参数用作属于接口的方法的返回值,或用作委托的返回类型。 但不能将协变类型参数用作接口方法的泛型类型约束。

如果接口的方法具有泛型委托类型的参数,则接口类型的协变类型参数可用于指定委托类型的逆变类型参数。

也可以将逆变类型参数用作接口方法的泛型类型约束。

接口或委托类型可以同时具有协变和逆变类型参数。

TypeLoadException 。

泛型接口中的差异 (Visual Basic)。

Variant 泛型接口和委托类型的列表

在 .NET Framework 4 中,下面的接口和委托类型具有协变和/或逆变类型参数。

类型 协变类型参数 逆变类型参数
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>  
Comparison<T>  
Converter<TInput,TOutput>
Func<TResult>  
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>
IComparable<T>  
Predicate<T>  
IComparer<T>  
IEnumerable<T>  
IEnumerator<T>  
IEqualityComparer<T>  
IGrouping<TKey,TElement>  
IOrderedEnumerable<TElement>  
IOrderedQueryable<T>  
IQueryable<T>  
 
【已更新最新开发文章,点击查看详细】

相关文章:

  • 2022-02-11
  • 2021-12-14
  • 2021-05-22
  • 2022-03-03
  • 2021-09-09
  • 2021-10-03
猜你喜欢
  • 2021-06-24
  • 2022-03-06
  • 2021-09-17
  • 2021-12-28
  • 2021-06-08
  • 2021-09-03
相关资源
相似解决方案