次の方法で共有


ジェネリックの共変性と反変性

共変性反変性 は、最初に指定されたよりも派生型 (より具体的) または派生型 (より具体的ではない) を使用する能力を指す用語です。 ジェネリック型パラメーターは共変性と反変性をサポートし、ジェネリック型の割り当てと使用の柔軟性を高めます。

型システムを参照する場合、共変性、反変性、および不変には次の定義があります。 この例では、 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のインスタンスの場合、MyMethodDerivedの新しいインスタンスを返します)。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> イエス

こちらも参照ください