泛型中的共變數和反變數
共變數和反變數這兩個詞,是指使用比原本所指定更多 (較明確) 或更少 (較不明確) 衍生類型的能力。 泛型類型參數支援共變數和反變數,可在指派和使用泛型類型時提供更大的彈性。
當您參考類型系統時,共變數、反變數和不可變數的定義如下。 範例中會假設名為 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>
,而是傳遞實作 ShapeAreaComparer
的 IComparer<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
的類別、繼承 Derived
的 Base
類別,和另一個具有 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> | 是 |