ジェネリックの共変性と反変性
"共変性" と "反変性" は、元の指定よりも強い派生型 (具体性が高い) と弱い派生型 (具体性が低い) を使用する能力を示す用語です。 ジェネリック型パラメーターは、ジェネリック型の代入と使用の柔軟性を向上させるために、共変性と反変性をサポートしています。
型システムにおいては、共変性、反変性、不変性は、次のように定義されます。 各例では、基底クラスが 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<Derived>
デリゲートの型パラメーター T
は反変であるため、結果として得られたデリゲートは Action<T> 型の変数に代入できます。 T
はパラメーター型を指定するため、コードはタイプ セーフです。 Action<Base>
型のデリゲートが Action<Derived>
型のデリゲートであるかのように呼び出される場合、その引数は Derived
型である必要があります。 メソッドのパラメーターは Base
型であるため、この引数は、基になるメソッドに常に安全に渡すことができます。
一般に、共変の型パラメーターはデリゲートの戻り値の型として使用でき、反変の型パラメーターはパラメーター型として使用できます。 インターフェイスについては、共変の型パラメーターをインターフェイスのメソッドの戻り値の型として使用でき、反変の型パラメーターをインターフェイスのメソッドのパラメーター型として使用できます。
共変性と反変性は、"変性" と総称されます。 共変または反変としてマークされていないジェネリック型パラメーターは、 不変と呼ばれます。 共通言語ランタイムにおける変性について、簡潔な概要を示します。
バリアント型パラメーターは、ジェネリック インターフェイスおよび汎用デリゲート型に制限されています。
ジェネリック インターフェイス型や汎用デリゲート型では、共変と反変の両方の型パラメーターを使用できます。
変性が適用されるのは参照型のみです。バリアント型パラメーターに対して値型を指定すると、その型パラメーターが、結果の構築型で不変になります。
変性は、デリゲートの組み合わせには適用されません。 つまり、
Action<Derived>
型とAction<Base>
型 (Visual Basic ではAction(Of Derived)
とAction(Of Base)
) の 2 つのデリゲートがある場合、結果はタイプ セーフになりますが、2 つ目のデリゲートに 1 つ目のデリゲートを組み合わせることはできません。 変性によって 2 つ目のデリゲートをAction<Derived>
型の変数に代入できますが、デリゲートを組み合わせることができるのは、それらの型が完全に一致している場合だけです。C# 9 以降では、共変の戻り値の型がサポートされています。 オーバーライドするメソッドで、オーバーライドされるメソッドより強い派生型の戻り値を宣言でき、オーバーライドする読み取り専用プロパティでより強い派生型を宣言できます。
共変の型パラメーターを持つジェネリック インターフェイス
共変の型パラメーターを持つジェネリック インターフェイスがいくつかあります (IEnumerable<T>、IEnumerator<T>、IQueryable<T>、IGrouping<TKey,TElement> など)。 これらのインターフェイスのすべての型パラメーターは共変のみであるため、型パラメーターはメンバーの戻り値の型だけに使用されます。
共変の型パラメーターの例を以下に示します。 ここでは 2 つの型が定義されています。 Base
には、 PrintBases
(Visual Basic では IEnumerable<Base>
) を受け取って要素を出力するIEnumerable(Of Base)
という静的メソッドがあります。 Derived
は Base
を継承します。 この例は、空の List<Derived>
(Visual Basic ではList(Of Derived)
) を作成し、その型を PrintBases
に渡して、キャストすることなく、 IEnumerable<Base>
型の変数に代入できることを示しています。 List<T> は、共変の型パラメーターを 1 つ持つ 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;
}
}
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> など)。 これらのインターフェイスの型パラメーターは反変のみであるため、これらの型パラメーターは、インターフェイスのメンバーのパラメーター型としてのみ使用されます。
反変の型パラメーターの例を以下に示します。 この例では、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> ) の比較子を渡すことができます。
新しい 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()
{
// 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
などの Action<T1,T2>汎用デリゲートには、反変のパラメーターの型があります。 したがって、より強い派生型のパラメーターと、より弱い派生型の戻り値 ( Func
汎用デリゲートの場合) を持つ変数に、デリゲートを代入できます。
Note
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
に格納しています。
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
のシグネチャが、構築された汎用デリゲート Func<Base, Derived>
(Visual Basic ではFunc(Of Base, Derived)
) のシグネチャと厳密に一致しています。 この例から、より強い派生型のパラメーターとより弱い派生型の戻り値を持つ変数やメソッド パラメーターにこの汎用デリゲートを格納できることと、そのためには、すべてのデリゲート型が汎用デリゲート型 Func<T,TResult>から構築されている必要があることがわかります。
これは重要なポイントです。 汎用デリゲートの型パラメーターにおける共変性と反変性の効果は、通常のデリゲート バインディングにおける共変性と反変性の効果 (「デリゲートの変性 (C#)」および「デリゲートの変性 (Visual Basic)」を参照) に似ていますが、 デリゲート バインディングの変性は、バリアント型パラメーターを持つ汎用デリゲート型だけでなく、すべてのデリゲート型で使用できます。 さらに、デリゲート バインディングの変性では、より限定的なパラメーターの型とより限定的でない戻り値の型を持つ任意のデリゲートにメソッドをバインドできますが、汎用デリゲートの代入を使用できるのは、両方のデリゲート型が同じジェネリック型定義から構築されている場合のみです。
デリゲート バインディングの変性とジェネリック型パラメーターの変性の両方の効果を組み合わせた例を以下に示します。 ここでは、3 つの型を含む型階層を定義しています。Type1
が最も弱い派生型で、Type3
が最も強い派生型です。 通常のデリゲート バインディングの変性を使用して、パラメーターの型が 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
キーワード) を使用します。 共変の型パラメーターは、インターフェイスに属するメソッドの戻り値として使用したり、デリゲートの戻り値の型として使用したりできます。 インターフェイス メソッドのジェネリック型制約として使用することはできません。
Note
インターフェイスのメソッドに汎用デリゲート型のパラメーターがある場合は、インターフェイス型の共変の型パラメーターを使用してデリゲート型の反変の型パラメーターを指定できます。
反変の型パラメーターをマークするには、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