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>
参考

コメント