泛型中的共變數和反變數

共變數反變數這兩個詞,是指使用比原本所指定更多 (較明確) 或更少 (較不明確) 衍生類型的能力。 泛型類型參數支援共變數和反變數,可在指派和使用泛型類型時提供更大的彈性。

當您參考類型系統時,共變數、反變數和不可變數的定義如下。 範例中會假設名為 Base 的基底類別,以及名為 Derived的衍生類別。

  • Covariance

    可讓您使用比原本指定更多衍生的類型。

    您可以將 IEnumerable<Derived> 的執行個體指派給 IEnumerable<Base> 類型的變數。

  • Contravariance

    可讓您使用比原本所指定更泛型 (較少衍生) 的類型。

    您可以將 Action<Base> 的執行個體指派給 Action<Derived> 類型的變數。

  • Invariance

    表示您只能使用原本指定的類型。 非變異泛型類型參數既不是 Covariant 也不是 Contravariant。

    您無法將 List<Base> 的執行個體指派給 List<Derived> 類型的變數,反之亦然。

Covariant 型別參數可讓您進行看起來很像一般多型的指派,如下列程式碼所示。

IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d

List<T> 類別會實作 IEnumerable<T> 介面,因此 List<Derived> (在 Visual Basic 中則為List(Of Derived) ) 會實作 IEnumerable<Derived>。 Covariant 型別參數會完成其餘工作。

而 Contravariance 看起來則違反直覺。 下列範例會建立 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());
Dim b As Action(Of Base) = Sub(target As Base)
                               Console.WriteLine(target.GetType().Name)
                           End Sub
Dim d As Action(Of Derived) = b
d(New Derived())

這看起來是反向的,但是在編譯和執行時是類型安全的程式碼。 Lambda 運算式符合指派的委派,所以定義的方法會接受一個 Base 類型的參數,並且沒有傳回值。 結果產生的委派可以指派給 Action<Derived> 類型的變數,因為 T 委派的 Action<T> 類型參數是 Contravariant。 程式碼是類型安全的,因為 T 指定參數類型。 當 Action<Base> 類型的委派當成 Action<Derived>類型的委派被叫用時,引數必須是 Derived類型。 此引數永遠可以安全地傳遞至基礎方法,因為方法的參數是 Base類型。

一般來說,Covariant 類型參數可以用來做為委派的傳回類型,而 Contravariant 類型參數可以用來做為參數類型。 例如,Covariant 類型參數可以用來做為介面方法的傳回類型,而 Contravariant 類型參數可以用來做為介面方法的參數類型。

共變數和反變數合稱為「變異數」。 未標示 Covariant 或 Contravariant 的泛型類型參數,稱為 Invariant參數。 通用語言執行平台中變異數事實的簡短摘要。

  • Variant 類型參數僅限為泛型介面和泛型委派類型。

  • 泛型介面或泛型委派類型可以同時具有 Covariant 和 Contravariant 類型參數。

  • 變異數只適用於參考類型,因此如果將 Variant 類型參數指定為實值類型,該類型參數最後建構的類型會是 Invariant。

  • 變異數不適用於委派組合。 也就是說,如果有分別適用於 Action<Derived>Action<Base> (在 Visual Basic 中則為Action(Of Derived)Action(Of Base) ) 類型的兩個委派,您無法將第二個委派與第一個委派組合 (雖然結果會是類型安全的)。 變異數允許將第二個委派指派給 Action<Derived>類型的變數,但是委派只有在類型完全相符時才能組合。

  • 從 C# 9 開始,支援 Covariant 傳回類型。 覆寫方法可以宣告其覆寫之方法的衍生傳回型別,而覆寫的唯讀屬性可以宣告更多衍生的類型。

具有 Covariant 類型參數的泛型介面

數個泛型介面具有 Covariant 類型參數,例如:IEnumerable<T>IEnumerator<T>IQueryable<T>IGrouping<TKey,TElement>。 這些介面的所有型別參數都是共變數,因此型別參數只能用於成員的傳回型別。

下列範例會說明 Covariant 類型參數。 這個範例定義兩個類型: 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>,後者具有單一 Covariant 類型參數。 Covariant 類型參數是可以使用 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;
    }
}
Imports System.Collections.Generic

Class Base
    Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
        For Each b As Base In bases
            Console.WriteLine(b)
        Next
    End Sub
End Class

Class Derived
    Inherits Base

    Shared Sub Main()
        Dim dlist As New List(Of Derived)()

        Derived.PrintBases(dlist)
        Dim bIEnum As IEnumerable(Of Base) = dlist
    End Sub
End Class

具有 Covariant 類型參數的泛型介面

數個泛型介面具有 Covariant 類型參數,例如:IComparer<T>IComparable<T>IEqualityComparer<T>。 這些介面只有 Contravariant 類型參數,因此類型參數只用來做為介面成員中的參數類型。

下列範例會說明 Contravariant 類型參數。 範例定義了包含MustInherit 屬性的抽象 (在 Visual Basic 中為 Shape ) Area 類別。 範例還定義了實作 ShapeAreaComparer (在 Visual Basic 中則為 IComparer<Shape> ) 的IComparer(Of Shape) 類別。 IComparer<T>.Compare 方法的實作是以 Area 屬性值為基礎,因此可以使用 ShapeAreaComparer 依區域排序 Shape 物件。

Circle 類別會繼承 Shape 並覆寫 Area。 此範例會使用採用 SortedSet<T> (在 Visual Basic 中為 Circle ) 的建構函式建立 IComparer<Circle> 物件的IComparer(Of Circle) 。 不過,此範例不會傳遞 IComparer<Circle>,而是傳遞實作 ShapeAreaComparerIComparer<Shape>物件。 當程式碼呼叫衍生程度較大類型 (Shape) 的比較子時,範例可以傳遞衍生程度較小類型 (Circle) 的比較子,因為 IComparer<T> 泛型介面的類型參數為 Contravariant。

將新的 Circle 物件加入至 SortedSet<Circle>時, IComparer<Shape>.Compare 物件的IComparer(Of Shape).Compare 方法 (在 Visual Basic 中為 ShapeAreaComparer 方法) 會在每次新項目與現有項目比較時呼叫。 這個方法的類型參數 (Shape) 相較於傳遞的類型 (Circle),其衍生程度較小,因此該呼叫具備類型安全。 反變數 (Contravariance) 可讓 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()
    {
        // You can pass ShapeAreaComparer, which implements IComparer<Shape>,
        // even though the constructor for SortedSet<Circle> expects
        // IComparer<Circle>, because type parameter T of IComparer<T> is
        // contravariant.
        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);
        }
    }
}

/* This code example produces the following output:

null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
 */
Imports System.Collections.Generic

MustInherit Class Shape
    Public MustOverride ReadOnly Property Area As Double
End Class

Class Circle
    Inherits Shape

    Private r As Double
    Public Sub New(ByVal radius As Double)
        r = radius
    End Sub
    Public ReadOnly Property Radius As Double
        Get
            Return r
        End Get
    End Property
    Public Overrides ReadOnly Property Area As Double
        Get
            Return Math.Pi * r * r
        End Get
    End Property
End Class

Class ShapeAreaComparer
    Implements System.Collections.Generic.IComparer(Of Shape)

    Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _
            Implements System.Collections.Generic.IComparer(Of Shape).Compare
        If a Is Nothing Then Return If(b Is Nothing, 0, -1)
        Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
    End Function
End Class

Class Program
    Shared Sub Main()
        ' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
        ' even though the constructor for SortedSet(Of Circle) expects 
        ' IComparer(Of Circle), because type parameter T of IComparer(Of T)
        ' is contravariant.
        Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
            From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}

        For Each c As Circle In circlesByArea
            Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
        Next
    End Sub
End Class

' This code example produces the following output:
'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979

具有 Variant 類型參數的泛型委派

Func 泛型委派 (例如 Func<T,TResult>) 具有 Covariant 傳回類型和 Contravariant 類型參數。 Action 泛型委派 (例如 Action<T1,T2>) 則具有 Contravariant 類型參數。 這表示可以將委派指派給具有衍生程度較大參數類型及 (如果是 Func 泛型委派) 衍生程度較小的傳回類型的變數。

注意

Func 泛型委派的最後一個泛型類型參數會指定委派簽章中的傳回值類型。 這個參數是 Covariant (out 關鍵字),而其他泛型類型參數則是 Contravariant (in 關鍵字)。

下列程式碼會說明這一點。 程式碼的第一段會定義名為 Base的類別、繼承 DerivedBase類別,和另一個具有 static 方法 (在 Visual Basic 中則為Shared 方法) 的 MyMethod類別。 這個方法會接受 Base 的執行個體,然後傳回 Derived 的執行個體 (如果引數為 Derived 的執行個體,MyMethod 即傳回此執行個體;如果引數為 Base 的執行個體,MyMethod 則傳回新的 Derived 執行個體)。在 Main() 中,這個範例會建立代表 Func<Base, Derived>Func(Of Base, Derived) (在 Visual Basic 中則為 MyMethod) 執行個體,並將它儲存在變數 f1 中。

public class Base {}
public class Derived : Base {}

public class Program
{
    public static Derived MyMethod(Base b)
    {
        return b as Derived ?? new Derived();
    }

    static void Main()
    {
        Func<Base, Derived> f1 = MyMethod;
Public Class Base
End Class
Public Class Derived
    Inherits Base
End Class

Public Class Program
    Public Shared Function MyMethod(ByVal b As Base) As Derived
        Return If(TypeOf b Is Derived, b, New Derived())
    End Function

    Shared Sub Main()
        Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod

程式碼的第二段顯示可以將委派指派給類型為 Func<Base, Base> (在 Visual Basic 中則為Func(Of Base, Base) ) 的變數,因為傳回類型是 Covariant。

// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())

程式碼的第三段顯示可以將委派指派給類型為 Func<Derived, Derived> (在 Visual Basic 中則為 Func(Of Derived, Derived)) 的變數,因為參數類型是 Contravariant。

// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())

程式碼的最後一段顯示結合 Contravariant 參數類型與 Covariant 傳回類型的效果,即可將委派指派給類型為 Func<Derived, Base> (在 Visual Basic 中則為Func(Of Derived, Base) ) 的變數。

// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())

非泛型委派中的變異數

在上述的程式碼中,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;

        // Covariant return type and contravariant parameter type.
        Func<Type3, Type1> f2 = f1;
        Type1 t1 = f2(new Type3());
    }
}
Public Class Type1
End Class
Public Class Type2
    Inherits Type1
End Class
Public Class Type3
    Inherits Type2
End Class

Public Class Program
    Public Shared Function MyMethod(ByVal t As Type1) As Type3
        Return If(TypeOf t Is Type3, t, New Type3())
    End Function

    Shared Sub Main()
        Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod

        ' Covariant return type and contravariant parameter type.
        Dim f2 As Func(Of Type3, Type1) = f1
        Dim t1 As Type1 = f2(New Type3())
    End Sub
End Class

定義 Variant 泛型介面與委派

Visual Basic 和 C# 提供可讓您將介面及委派的泛型類型參數標記為 Covariant 或 Contravariant 的關鍵字。

Covariant 類型參數是以 out 關鍵字 (在 Visual Basic 中為 Out 關鍵字) 來標記。 您可以使用 Covariant 類型參數當做屬於介面之方法的傳回值,或當做委派的傳回類型。 但是,您不能將 Covariant 類型參數當做介面方法的泛型類型條件約束使用。

注意

如果介面的方法具有泛型委派類型的參數,就可以使用介面類型的 Covariant 類型參數指定委派類型的 Contravariant 類型參數。

Contravariant 類型參數是以 in 關鍵字 (在 Visual Basic 中為 In 關鍵字) 來標記。 您可以使用 Contravariant 類型參數當做屬於介面之方法的參數類型,或當做委派的參數類型。 此外,您也可以將 Contravariant 類型參數當做介面方法的泛型類型條件約束使用。

只有介面類型和委派類型可以有 Variant 類型參數。 介面或委派類型可以同時具有 Covariant 和 Contravariant 類型參數。

Visual Basic 和 C# 不允許您違反使用 Covariant 和 Contravariant 型別參數的規則,也不允許您將 Covariant 和 Contravariant 附註加入至介面及委派以外類型的型別參數。

如需詳細資訊與範例程式碼,請參閱泛型介面中的變異數 (C#)泛型介面中的變異數 (Visual Basic)

類型清單

下列介面和委派類型具有 Covariant 及/或 Contravariant 類型參數。

類型 Covariant 類型參數 Contravariant 類型參數
Action<T> 移至 Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> Yes
Comparison<T> .是
Converter<TInput,TOutput> .是 .是
Func<TResult> Yes
Func<T,TResult> 移至 Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> Yes .是
IComparable<T> .是
Predicate<T> .是
IComparer<T> .是
IEnumerable<T> .是
IEnumerator<T> .是
IEqualityComparer<T> .是
IGrouping<TKey,TElement> .是
IOrderedEnumerable<TElement> .是
IOrderedQueryable<T> .是
IQueryable<T>

另請參閱