C# 共変性と反変性(in / out)
共変性(Covariance)と反変性(Contravariance)は、ジェネリック型の型パラメータに対する変換の規則を定めるものです。in と out キーワードを使って指定します。
問題の背景
Dog は Animal を継承していても、List<Dog> は List<Animal> として扱えません。
class Animal { }
class Dog : Animal { }
List<Dog> dogs = new List<Dog>();
// List<Animal> animals = dogs; // コンパイルエラー!これは型安全性を守るためです。もし許可されると、animals.Add(new Cat()) のような操作で List<Dog> に猫が入ってしまいます。
共変性(out キーワード)
共変性は「出力のみに使う型パラメータ」に適用します。out キーワードで指定します。
public interface IProducer<out T>
{
T Produce(); // T を返す(出力)のみ
}共変の場合、IProducer<Dog> を IProducer<Animal> として扱えます。
class AnimalProducer : IProducer<Animal>
{
public Animal Produce() => new Animal();
}
class DogProducer : IProducer<Dog>
{
public Dog Produce() => new Dog();
}
IProducer<Animal> producer = new DogProducer(); // OK
Animal animal = producer.Produce(); // Dog が返る反変性(in キーワード)
反変性は「入力のみに使う型パラメータ」に適用します。in キーワードで指定します。
public interface IConsumer<in T>
{
void Consume(T item); // T を受け取る(入力)のみ
}反変の場合、IConsumer<Animal> を IConsumer<Dog> として扱えます(方向が逆)。
class AnimalConsumer : IConsumer<Animal>
{
public void Consume(Animal item) => Console.WriteLine("Animal を消費");
}
IConsumer<Dog> dogConsumer = new AnimalConsumer(); // OK
dogConsumer.Consume(new Dog()); // Dog は Animal でもあるので OK共変性と反変性の比較
より派生した型を、基底型として扱える。出力専用。
より基底の型を、派生型として扱える。入力専用。
.NET の実例
.NET Framework には共変・反変を活用したインターフェースがあります。
共変。IEnumerable<Dog> を IEnumerable<Animal> として扱える。
反変。IComparer<Animal> を IComparer<Dog> として扱える。
戻り値の型は共変。
引数の型は反変。
IEnumerable<Dog> dogs = new List<Dog> { new Dog() };
IEnumerable<Animal> animals = dogs; // 共変により OK
Action<Animal> animalAction = a => Console.WriteLine("Animal");
Action<Dog> dogAction = animalAction; // 反変により OK制限事項
共変・反変は、インターフェースとデリゲートにのみ適用できます。クラスには適用できません。また、out を付けた型パラメータは戻り値にのみ、in を付けた型パラメータは引数にのみ使用できます。
共変性と反変性を理解することで、より柔軟で安全な API 設計が可能になります。