C# ジェネリック インターフェイスの共変性と反変性

C#

C#では、version 4(.NET Framework 4)の時代から変性がサポートされています。ここではまず、C#におけるジェネリック・インターフェイスの共変性(Covariance)と反変性(Contravariance)を考えます。

サンプルコードの前提

Cat型はAnimal型を継承しており、CatはAnimalに代入可能、暗黙でキャストされます。

Cat <: Animal

    class Animal { public void DoSomething() { /****/ }  }
    class Cat : Animal { }

共変性 Covariance

ジェネリック・インターフェイス I<Cat> がI<Animal>に変換可能であるとき、Interface<T>は共変性を持ちます。I<Cat> <: I<Animal>

// CODE ex1
class Animal {}
class Cat: Animal {}
Animal animal = new Animal();
Animal animal2 = new Cat();    // CatはAnimalに暗黙で変換可能。

IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;

この例では、暗黙でパラメータの型変換が行われ、IEnumerable<Cat>は、IEnumerable<Animal>に代入できます。また、メソッドの引数として渡す場合も暗黙で変換されます。

// CODE ex2
public class Animal { public void DoSomething() { /* ... */} }
public class Cat : Animal { }

public class TestCovariant
{
    public static void Main()
    {
        new TestCovariant().main();
    }

    public void main()
    {
        IEnumerable<Cat> cats = 
            new List<Cat>() { new Cat("Mike"), new Cat("Chatra"), new Cat("Kuro") };
        PrintName(cats);
    }

    public void PrintName(IEnumerable<Animal> animals)
    {
        foreach (Animal animal in animals)
        {
            animal.DoSomething();
        }
    }
}

PrintName( )は、IEnumerable<Animal>を想定していますが、そこにIEnumnerable<Cat>を渡して暗黙の変換を行っています。

IEnumnerable<T>の定義は、interface IEnumerable<out T> : IEnumerable となっています。
このようにジェネリックインターフェイスにおいて、そのジェネリックパラメータが戻り値や出力で使用される場合には<out T>を指定することで、共変性をサポートすることができます。

反変性 Contravariance

次に反変性です。共変性とは互換性の方向が逆になります。

Cat <: Animal
I<Animal> <: I<Cat>

public class Test
{
    public static void Main()
    {
        var test = new Test();
        test.TestContraVariant();
    }

    public void TestContraVariant()
    {
        List<Cat> cats = [new Cat("Mike"), new Cat("茶-tra"), new Cat("Kuro")];
        cats.Sort(new AnimalComparer());
    }
}

class AnimalComparer : IComparer<Animal>
{
    public int Compare(Animal? x, Animal? y)
    {
        if (x.Name == y.Name) return 0;
        else
        {
            return x.Name.CompareTo(y.Name);
        }
    }
}

List<Cat>のSortメソッドは、引数にIComparer<Cat>を想定しますが、ここではIComparer<Animal>を渡して、暗黙でIComparer<Cat>に変換されています。

ここでIComparerの定義は、interface IComparer<in T>となっています。引数、入力パラメータについて互換性がサポートされるのですが、パラメータの型とは反対向きの互換性となります。このようにジェネリックインターフェイスにおいて、そのジェネリックパラメータが引数など入力で使用される場合には<in T>を指定することで、反変性をサポートすることができます。

反変性の実装

反変性を持つジェネリックインターフェイスの実装サンプルです。

    interface IFarm<in T>
    {
        public void Add(T value);
    }

    public class Farm_<T> : IFarm<T> 
    {
        public void Add(T value)
        {
            /* ... */
        }
    }

    IFarm<Animal> animal_farm = new Farm_<Animal>();
    IFarm<Cat> cat_farm = new Farm_<Cat>();
    cat_farm = animal_farm;   // derived <- base

ジェネリックインターフェイス IFarmの型パラメータは<in T>になっていますので、入力パラメータの型においての互換性は、反変性となります。IFarm<Cat>にはIFarm<Animal>が暗黙で代入可能です。つまり、IFarm<派生クラス>に対してIFarm<基底クラス>が代入可能ということです。

変性をサポートする定義済のインターフェイス

共変

IEnumerable<out T>
IEnumerator<out T>
IQueryable<out T>
IGrouping<out TKey, out TElement> (TKey と TElement は共変)
IReadOnlyList<out T>
IReadOnlyCollection<out T>

反変

IComparer<in T>
IEqualityComparer<in T>
IComparable<in T>

参考

ジェネリック インターフェイスの分散 - C#
.NET Framework 4 および 4.5 の既存のインターフェイスに関して更新された情報を含め、ジェネリック インターフェイスに対する変性のサポートの情報を表示します。

コメント

タイトルとURLをコピーしました