Share via


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

共変および反変のジェネリック型パラメーターでは、ジェネリック型の代入と使用の柔軟性が向上しています。 たとえば、共変の型パラメーターでは、通常のポリモーフィズムと非常によく似た代入を行うことができます。 たとえば、Base という基本クラスと、Derived という派生クラスがあるとします。 ポリモーフィズムによって、Derived のインスタンスを Base 型の変数に代入できます。 同様に、IEnumerable<T> インターフェイスの型パラメーターは共変であるため、次のコードに示すように、IEnumerable<Derived> (Visual Basic では IEnumerable(Of Derived)) のインスタンスを IEnumerable<Base> 型の変数に代入できます。

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

List<T> クラスは IEnumerable<T> インターフェイスを実装するため、List<Derived> (Visual Basic では List(Of Derived)) は IEnumerable<Derived> を実装します。 共変の型パラメーターが後の処理を行います。

共変性はポリモーフィズムに似ているため、非常に自然に見えます。 一方、反変性は直感に反するように見えます。 次の例では、Action<Base> 型 (Visual Basic では Action(Of Base)) のデリゲートを作成し、次にそのデリゲートを Action<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())
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());

これは逆方向のように見えますが、コンパイルして実行できるタイプ セーフ コードです。 ラムダ式は代入先のデリゲートに一致するため、Base 型のパラメーターを 1 つ受け取って戻り値を返さないメソッドを定義します。 Action<T> デリゲートの型パラメーター T は反変であるため、結果として得られたデリゲートは Action<Derived> 型の変数に代入できます。 T はパラメーター型を指定するため、コードはタイプ セーフです。 Action<Base> 型のデリゲートが Action<Derived> 型のデリゲートであるかのように呼び出される場合、その引数は Derived 型である必要があります。 メソッドのパラメーターは Base 型であるため、この引数は、基になるメソッドに常に安全に渡すことができます。

一般に、共変の型パラメーターはデリゲートの戻り値の型として使用でき、反変の型パラメーターはパラメーター型として使用できます。 インターフェイスについては、共変の型パラメーターをインターフェイスのメソッドの戻り値の型として使用でき、反変の型パラメーターをインターフェイスのメソッドのパラメーター型として使用できます。

共変性と反変性は、"分散" と総称されます。 共変または反変としてマークされていないジェネリック型パラメーターは、不変と呼ばれます。 共通言語ランタイムにおける分散について、簡潔な概要を示します。

  • .NET Framework Version 4 のバリアント型パラメーターは、ジェネリック インターフェイス型と汎用デリゲート型に制限されています。

  • ジェネリック インターフェイス型や汎用デリゲート型では、共変と反変の両方の型パラメーターを使用できます。

  • 分散が適用されるのは参照型のみです。バリアント型パラメーターに対して値型を指定すると、その型パラメーターが、結果の構築型で不変になります。

  • 分散は、デリゲートの組み合わせには適用されません。 つまり、Action<Derived> 型と Action<Base> 型 (Visual Basic では Action(Of Derived) と Action(Of Base)) の 2 つのデリゲートがある場合、結果はタイプ セーフになりますが、2 つ目のデリゲートに 1 つ目のデリゲートを組み合わせることはできません。 分散によって 2 つ目のデリゲートを Action<Derived> 型の変数に代入できますが、デリゲートを組み合わせることができるのは、それらの型が完全に一致している場合だけです。

以降では、共変と反変の型パラメーターについて詳しく説明します。

  • 共変の型パラメーターを持つジェネリック インターフェイス

  • 反変のジェネリック型パラメーターを持つジェネリック インターフェイス

  • バリアント型パラメーターを持つ汎用デリゲート

  • バリアント ジェネリック インターフェイスとバリアント汎用デリゲートの定義

  • バリアント ジェネリック インターフェイス型とバリアント汎用デリゲート型の一覧

共変の型パラメーターを持つジェネリック インターフェイス

.NET Framework 4 以降には、共変の型パラメーターを持つジェネリック インターフェイスがいくつかあります (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> は、共変の型パラメーターを 1 つ持つ IEnumerable<T> を実装します。 IEnumerable<Derived> のインスタンスを IEnumerable<Base> の代わりに使用できるのは、この共変の型パラメーターがあるためです。

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
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;
    }
}

ページのトップへ

反変のジェネリック型パラメーターを持つジェネリック インターフェイス

.NET Framework 4 以降には、反変の型パラメーターを持つジェネリック インターフェイスがいくつかあります (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 をオーバーライドします。 この例では、IComparer<Circle> (Visual Basic では IComparer(Of 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 から派生した型の混合コレクションを並べ替えることができるようになります。

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
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
 */

ページのトップへ

バリアント型パラメーターを持つ汎用デリゲート

.NET Framework 4 では、Func<T, TResult> などの Func 汎用デリゲートに、共変の戻り値の型と反変のパラメーターの型があります。 Action<T1, T2> などの Action 汎用デリゲートには、反変のパラメーターの型があります。 したがって、より強い派生型のパラメーターと、より弱い派生型の戻り値 (Func 汎用デリゲートの場合) を持つ変数に、デリゲートを代入できます。

メモメモ

Func 汎用デリゲートの最後のジェネリック型パラメーターは、デリゲート シグネチャの戻り値の型を指定します。他のジェネリック型パラメーターは反変 (in キーワード) ですが、この最後のジェネリック型パラメーターは共変 (out キーワード) です。

次に例を示します。 コードの最初の部分では、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 
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
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;

コードの 2 番目の部分は、このデリゲートを Func<Base, Base> (Visual Basic では Func(Of Base, Base)) 型の変数に代入できることを示しています。これは、戻り値の型が共変であるためです。

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

コードの 3 番目の部分は、このデリゲートを Func<Derived, Derived> (Visual Basic では Func(Of Derived, Derived)) 型の変数に代入できることを示しています。これは、パラメーターの型が反変であるためです。

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

コードの最後の部分は、このデリゲートを Func<Derived, Base> (Visual Basic では Func(Of Derived, Base)) 型の変数に代入できることを示しています。これは、反変のパラメーターの型と共変の戻り値の型の両方の効果の組み合わせによるものです。

' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = 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>) から構築されている必要があります。

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
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());
    }
}

ページのトップへ

バリアント ジェネリック インターフェイスとバリアント汎用デリゲートの定義

.NET Framework 4 以降では、Visual Basic と C# の両方で、インターフェイスやデリゲートのジェネリック型パラメーターを共変または反変としてマークするためのキーワードがサポートされています。

メモメモ

ジェネリック型パラメーターの分散注釈は .NET Framework Version 2.0 以降の共通言語ランタイムでサポートされていますが、.NET Framework 4 までは、クラスを Ilasm.exe (MSIL アセンブラー) でコンパイルするか、動的アセンブリに出力して Microsoft Intermediate Language (MSIL) を使用して、これらの注釈を含むジェネリック クラスを定義する必要がありました。

共変の型パラメーターをマークするには、out キーワード (Visual Basic では Out キーワード、MSIL アセンブラーでは +) を使用します。 共変の型パラメーターは、インターフェイスに属するメソッドの戻り値として使用したり、デリゲートの戻り値の型として使用したりできます。 インターフェイス メソッドのジェネリック型制約として使用することはできません。

メモメモ

インターフェイスのメソッドに汎用デリゲート型のパラメーターがある場合は、インターフェイス型の共変の型パラメーターを使用してデリゲート型の反変の型パラメーターを指定できます。

反変の型パラメーターをマークするには、in キーワード (Visual Basic では In キーワード、MSIL アセンブラーでは -) を使用します。 反変の型パラメーターは、インターフェイスに属するメソッドのパラメーターの型として使用したり、デリゲートのパラメーターの型として使用したりできます。 インターフェイス メソッドのジェネリック型制約として使用することもできます。

バリアント型パラメーターを持つことができるのは、インターフェイス型とデリゲート型だけです。 インターフェイス型やデリゲート型は、共変と反変の両方の型パラメーターを持つことができます。

Visual Basic と C# では、共変および反変の型パラメーターの使用規則に違反したり、インターフェイスとデリゲート以外の型の型パラメーターに共変性や反変性の注釈を追加したりすることは許可されません。 MSIL アセンブラーではそのようなチェックは行われませんが、規則に違反する型を読み込もうとすると TypeLoadException がスローされます。

プログラム例および詳細については、「ジェネリック インターフェイスの分散 (C# および Visual Basic)」を参照してください。

ページのトップへ

バリアント ジェネリック インターフェイス型とバリアント汎用デリゲート型の一覧

共変または反変、あるいはその両方の型パラメーターを持つ .NET Framework 4 のインターフェイス型とデリゲート型を以下に示します。 

共変の型パラメーター

反変の型パラメーター

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>

ページのトップへ

参照

概念

デリゲートの分散 (C# および Visual Basic)

その他の技術情報

共変性と反変性 (C# および Visual Basic)