Ответ
Модификаторы in и out в C# используются для объявления вариантности (variance) в универсальных (обобщенных) интерфейсах и делегатах. Они позволяют обеспечить безопасное приведение обобщенного типа с одним параметром типа к обобщенному типу с другим параметром типа.
-
Ковариантность (
out): Позволяет использовать более производный тип, чем заданный изначально. Ключевое словоoutуказывает, что параметр типа используется только как возвращаемое значение (выходной параметр).// Объявление ковариантного интерфейса interface IProducer<out T> { T Produce(); // T используется только на выходе } class Animal { } class Dog : Animal { } IProducer<Dog> dogProducer = ...; // Безопасное приведение: IProducer<Dog> -> IProducer<Animal> IProducer<Animal> animalProducer = dogProducer; // Ковариантность разрешена благодаря `out` Animal a = animalProducer.Produce(); // Вернет Dog, который является AnimalПример в .NET:
IEnumerable<out T>. -
Контравариантность (
in): Позволяет использовать более универсальный (базовый) тип, чем заданный изначально. Ключевое словоinуказывает, что параметр типа используется только как входной параметр.// Объявление контравариантного интерфейса interface IConsumer<in T> { void Consume(T item); // T используется только на входе } IConsumer<Animal> animalConsumer = ...; // Безопасное приведение: IConsumer<Animal> -> IConsumer<Dog> IConsumer<Dog> dogConsumer = animalConsumer; // Контравариантность разрешена благодаря `in` dogConsumer.Consume(new Dog()); // Метод ожидает Animal, а получает Dog (это безопасно)Пример в .NET:
Action<in T>.
Правила и почему это безопасно:
out(ковариантность): Гарантирует, чтоTбудет использоваться только для возврата значений. Вы не сможете объявить метод, принимающийTкак параметр. Это безопасно, потому что потребитель интерфейсаIProducer<Animal>ожидает получитьAnimal, а реализацияIProducer<Dog>всегда вернетDog, который является подтипомAnimal.in(контравариантность): Гарантирует, чтоTбудет использоваться только как входной параметр. Вы не сможете объявить метод, возвращающийT. Это безопасно, потому что потребитель интерфейсаIConsumer<Dog>будет передавать в методConsumeаргумент типаDog, а реализацияIConsumer<Animal>готова принять любойAnimal, включаяDog.
Итог: out позволяет присваивать коллекции производных типов коллекциям базовых типов (можно читать). in позволяет присваивать делегаты/обработчики, работающие с базовыми типами, делегатам, работающим с производными типами (можно писать). Без этих модификаторов обобщенные типы являются инвариантными.