共変性 と 反変性 は、最初に指定されたよりも派生型 (より具体的) または派生型 (より具体的ではない) を使用する能力を指す用語です。 ジェネリック型パラメーターは共変性と反変性をサポートし、ジェネリック型の割り当てと使用の柔軟性を高めます。
型システムを参照する場合、共変性、反変性、および不変には次の定義があります。 この例では、 Base
という名前の基底クラスと、 Derived
という名前の派生クラスを想定しています。
Covariance
最初に指定した型よりも多くの派生型を使用できます。
IEnumerable<Derived>
のインスタンスをIEnumerable<Base>
型の変数に割り当てることができます。Contravariance
最初に指定した型よりもジェネリック (派生が少ない) 型を使用できます。
Action<Base>
のインスタンスをAction<Derived>
型の変数に割り当てることができます。Invariance
つまり、最初に指定した型のみを使用できます。 不変ジェネリック型パラメーターは、共変でも反変でもありません。
List<Base>
のインスタンスをList<Derived>
型の変数に割り当てることはできません。
共変型パラメーターを使用すると、次のコードに示すように、通常の ポリモーフィズムとよく似た割り当てを行うことができます。
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>
を実装します。 共変型パラメーターは、残りの処理を行います。
一方、反変性は直感に反するようです。 次の例では、 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())
これは逆のようですが、コンパイルして実行するのはタイプセーフなコードです。 ラムダ式は、割り当てられているデリゲートと一致するため、 Base
型の 1 つのパラメーターを受け取り、戻り値を持たないメソッドを定義します。 結果のデリゲートは、Action<T> デリゲートの型パラメーター T
が反変であるため、Action<Derived>
型の変数に割り当てることができます。
T
はパラメーター型を指定するため、コードはタイプ セーフです。
Action<Base>
型のデリゲートがAction<Derived>
型のデリゲートであるかのように呼び出される場合、その引数はDerived
型である必要があります。 この引数は、基になるメソッドに安全に渡すことができます。これは、メソッドのパラメーターが Base
型であるためです。
一般に、共変型パラメーターはデリゲートの戻り値の型として使用でき、反変型パラメーターはパラメーター型として使用できます。 インターフェイスの場合、共変型パラメーターはインターフェイスのメソッドの戻り値の型として使用でき、反変型パラメーターはインターフェイスのメソッドのパラメーター型として使用できます。
共変性と反変性は、まとめて 分散と呼ばれます。 共変または反変としてマークされていないジェネリック型パラメーターは、 インバリアントと呼ばれます。 共通言語ランタイムの差異に関するファクトの簡単な概要:
バリアント型パラメーターは、ジェネリック インターフェイスとジェネリック デリゲート型に制限されます。
ジェネリック インターフェイスまたはジェネリック デリゲート型は、共変型パラメーターと反変型パラメーターの両方を持つことができます。
差異は参照型にのみ適用されます。バリアント型パラメーターに値型を指定した場合、その型パラメーターは結果として構築された型に対して不変です。
分散はデリゲートの組み合わせには適用されません。 つまり、
Action<Derived>
型とAction<Base>
型の 2 つのデリゲート (Visual Basic ではAction(Of Derived)
とAction(Of Base)
) を指定すると、2 番目のデリゲートを最初のデリゲートと組み合わせることはできませんが、結果は型セーフになります。 分散を使用すると、2 番目のデリゲートをAction<Derived>
型の変数に割り当てることができますが、デリゲートは型が完全に一致する場合にのみ結合できます。C# 9 以降では、共変の戻り値の型がサポートされています。 オーバーライドするメソッドは、オーバーライドするメソッドにより派生した戻り値の型を宣言でき、オーバーライドされる読み取り専用プロパティでは、より派生型を宣言できます。
共変型パラメーターを持つジェネリック インターフェイス
一部のジェネリック インターフェイスには、 IEnumerable<T>、 IEnumerator<T>、 IQueryable<T>、 IGrouping<TKey,TElement>などの共変型パラメーターがあります。 これらのインターフェイスのすべての型パラメーターは共変であるため、型パラメーターはメンバーの戻り値の型にのみ使用されます。
次の例は、共変型パラメーターを示しています。 この例では、2 つの型を定義します。Base
には、IEnumerable<Base>
(Visual Basic でIEnumerable(Of Base)
) を受け取り、要素を出力する PrintBases
という名前の静的メソッドがあります。
Derived
は、Base
から継承されます。 この例では、空の List<Derived>
(Visual Basic でList(Of Derived)
) を作成し、この型を PrintBases
に渡し、キャストせずに IEnumerable<Base>
型の変数に割り当てることができることを示します。
List<T> は、単一の共変型パラメーターを持つ IEnumerable<T>を実装します。 共変型パラメーターは、IEnumerable<Base>
の代わりにIEnumerable<Derived>
のインスタンスを使用できる理由です。
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
反変型パラメーターを持つジェネリック インターフェイス
いくつかのジェネリック インターフェイスには反変型パラメーターがあります。たとえば、 IComparer<T>、 IComparable<T>、 IEqualityComparer<T>などです。 これらのインターフェイスには反変型パラメーターしかないため、型パラメーターはインターフェイスのメンバーのパラメーター型としてのみ使用されます。
反変型パラメーターの例を次に示します。 この例では、Area
プロパティを持つ抽象 (Visual Basic のMustInherit
) Shape
クラスを定義します。 この例では、IComparer<Shape>
(Visual Basic のIComparer(Of Shape)
) を実装するShapeAreaComparer
クラスも定義します。
IComparer<T>.Compare メソッドの実装は、Area
プロパティの値に基づいているため、ShapeAreaComparer
を使用して、Shape
オブジェクトを領域別に並べ替えることができます。
Circle
クラスはShape
を継承し、Area
をオーバーライドします。 この例では、(Visual Basic でIComparer(Of Circle)
) IComparer<Circle>
を受け取るコンストラクターを使用して、Circle
オブジェクトのSortedSet<T>を作成します。 ただし、この例では、IComparer<Circle>
を渡す代わりに、IComparer<Shape>
を実装するShapeAreaComparer
オブジェクトを渡します。 この例では、IComparer<T>ジェネリック インターフェイスの型パラメーターが反変であるため、コードがより派生型 (Circle
) の比較子を呼び出すときに、派生型 (Shape
) の比較子を渡すことができます。
新しいCircle
オブジェクトがSortedSet<Circle>
に追加されると、新しい要素が既存の要素と比較されるたびに、ShapeAreaComparer
オブジェクトのIComparer<Shape>.Compare
メソッド (Visual Basic の IComparer(Of Shape).Compare
メソッド) が呼び出されます。 メソッド (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()
{
// 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
バリアント型パラメーターを持つジェネリック デリゲート
Func<T,TResult>などのFunc
ジェネリック デリゲートには、共変の戻り値の型と反変のパラメーター型があります。
Action<T1,T2>などのAction
ジェネリック デリゲートには、反変のパラメーター型があります。 つまり、デリゲートは、より多くの派生パラメーター型を持つ変数に割り当てることができ、( Func
ジェネリック デリゲートの場合)、派生した戻り値の型が少なくなります。
注
Func
ジェネリック デリゲートの最後のジェネリック型パラメーターは、デリゲート シグネチャの戻り値の型を指定します。 共変 (out
キーワード) ですが、他のジェネリック型パラメーターは反変 (in
キーワード) です。
次のコードは、これを示しています。 最初のコードでは、Base
という名前のクラス、Base
を継承する Derived
という名前のクラス、および MyMethod
という名前の static
メソッド (Visual Basic のShared
) を持つ別のクラスを定義します。 このメソッドは、 Base
のインスタンスを受け取り、 Derived
のインスタンスを返します。 (引数がDerived
のインスタンスの場合、MyMethod
はそれを返します。引数がBase
のインスタンスの場合、MyMethod
はDerived
の新しいインスタンスを返します)。Main()
では、MyMethod
を表すFunc<Base, Derived>
(Visual Basic では Func(Of Base, Derived)
) のインスタンスを作成し、変数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
2 番目のコードは、戻り値の型が共変であるため、デリゲートを Func<Base, Base>
型 (Visual Basic ではFunc(Of Base, Base)
) の変数に割り当てることができることを示しています。
// 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())
3 番目のコードは、パラメーター型が反変であるため、デリゲートを Func<Derived, Derived>
型 (Visual Basic ではFunc(Of Derived, Derived)
) の変数に割り当てることができることを示しています。
// 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())
最後のコードは、反変パラメーター型と共変の戻り値の型の効果を組み合わせて、デリゲートを 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
のシグネチャは、構築されたジェネリック デリゲートのシグネチャ (Visual Basic のFunc(Of Base, Derived)
) Func<Base, Derived>
と正確に一致します。 この例では、すべてのデリゲート型がジェネリック デリゲート型 Func<T,TResult>から構築されている限り、このジェネリック デリゲートは、より多くの派生パラメーター型とより派生の少ない戻り値の型を持つ変数またはメソッド パラメーターに格納できることを示しています。
これは重要なポイントです。 ジェネリック デリゲートの型パラメーターにおける共変性と反変性の効果は、通常のデリゲート バインディングにおける共変性と反変性の影響と似ています (「 デリゲートの分散 (C#)」 と「 デリゲートの分散 (Visual Basic)」を参照)。 ただし、デリゲート バインドの分散は、バリアント型パラメーターを持つジェネリック デリゲート型だけでなく、すべてのデリゲート型で機能します。 さらに、デリゲート バインドの分散により、より制限の厳しいパラメーター型と制限の緩い戻り値の型を持つデリゲートにメソッドをバインドできます。一方、ジェネリック デリゲートの割り当ては、両方のデリゲート型が同じジェネリック型定義から構築されている場合にのみ機能します。
次の例は、デリゲート バインディングの分散とジェネリック型パラメーターの分散の組み合わせ効果を示しています。 この例では、最小派生 (Type1
) から最も派生 (Type3
) までの 3 つの型を含む型階層を定義します。 通常のデリゲート バインディングの分散は、パラメーター型が Type1
で、戻り値の型が Type3
のメソッドを、パラメーター型が Type2
で戻り値の型が Type2
のジェネリック デリゲートにバインドするために使用されます。 その後、ジェネリック デリゲート型の共変性と反変性を使用して、ジェネリック デリゲート型に Type3
型のパラメーターと Type1
の戻り値の型を持つ別の変数に、結果のジェネリック デリゲートが割り当てられます。 2 番目の代入では、変数型とデリゲート型の両方を同じジェネリック型定義から構築する必要があります。この場合は、 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
バリアント ジェネリック インターフェイスとデリゲートを定義する
Visual Basic と C# には、インターフェイスとデリゲートのジェネリック型パラメーターを共変または反変としてマークできるキーワードがあります。
共変型パラメーターは、 out
キーワード (Visual Basic では Out
キーワード) でマークされます。 共変型パラメーターは、インターフェイスに属するメソッドの戻り値として、またはデリゲートの戻り値の型として使用できます。 インターフェイス メソッドのジェネリック型制約として共変型パラメーターを使用することはできません。
注
インターフェイスのメソッドにジェネリック デリゲート型のパラメーターがある場合、インターフェイス型の共変型パラメーターを使用して、デリゲート型の反変型パラメーターを指定できます。
反変型パラメーターは、 in
キーワード (Visual Basic では In
キーワード) でマークされます。 反変型パラメーターは、インターフェイスに属するメソッドのパラメーターの型として、またはデリゲートのパラメーターの型として使用できます。 インターフェイス メソッドのジェネリック型制約として反変型パラメーターを使用できます。
バリアント型パラメーターを持つことは、インターフェイス型とデリゲート型のみです。 インターフェイスまたはデリゲート型は、共変型パラメーターと反変型パラメーターの両方を持つことができます。
Visual Basic と C# では、共変型パラメーターと反変型パラメーターを使用するための規則に違反したり、インターフェイスやデリゲート以外の型パラメーターに共変性と反変性注釈を追加したりすることはできません。
詳細とコード例については、「 ジェネリック インターフェイスの分散 (C#) 」および「 ジェネリック インターフェイスの分散 (Visual Basic)」を参照してください。
型の一覧
次のインターフェイスとデリゲート型には、共変または反変の型パラメーターがあります。
タイプ | 共変型パラメーター | 反変型パラメーター |
---|---|---|
Action<T> から 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<T,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> | イエス |
こちらも参照ください
.NET