С помощью <out T>
вы можете рассматривать ссылку на интерфейс как ссылку вверх по иерархии.
С помощью <in T>
вы можете рассматривать ссылку на интерфейс как ссылку внизу в иерархии.
Позвольте мне попытаться объяснить это более английскими терминами.
Допустим, вы получаете список животных из своего зоопарка и собираетесь их обработать. У всех животных (в вашем зоопарке) есть имя и уникальный идентификатор. Некоторые животные - млекопитающие, некоторые - рептилии, некоторые - земноводные, некоторые - рыбы и т. Д., Но все они животные.
Итак, со своим списком животных (который содержит животных разных типов) вы можете сказать, что у всех животных есть имя, поэтому, очевидно, было бы безопасно получить имена всех животных.
Однако что, если у вас есть только список рыб, но нужно обращаться с ними как с животными, это работает? Интуитивно это должно работать, но в C # 3.0 и более ранних версиях этот фрагмент кода не компилируется:
IEnumerable<Animal> animals = GetFishes();
Причина этого в том, что компилятор не «знает», что вы собираетесь или можете делать с коллекцией животных после того, как вы ее получили. Насколько он знает, может быть способ IEnumerable<T>
вернуть объект в список, и это потенциально позволит вам поместить животное, не являющееся рыбой, в коллекцию, которая должна содержать только рыбу.
Другими словами, компилятор не может гарантировать, что это запрещено:
animals.Add(new Mammal("Zebra"));
Таким образом, компилятор просто отказывается компилировать ваш код. Это ковариация.
Давайте посмотрим на контравариантность.
Поскольку наш зоопарк может содержать всех животных, он, безусловно, может ловить рыбу, поэтому давайте попробуем добавить немного рыбы в наш зоопарк.
В C # 3.0 и более ранних версиях это не компилируется:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Здесь компилятор может разрешить этот фрагмент кода, даже если метод возвращает результат List<Animal>
просто потому, что все рыбы - животные, поэтому, если мы просто изменим типы на это:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Тогда это сработает, но компилятор не может определить, что вы не пытаетесь это сделать:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Поскольку список фактически является списком животных, это не допускается.
Таким образом, противоречие и ковариация - это то, как вы относитесь к ссылкам на объекты и что вам разрешено с ними делать.
in
И out
ключевые слова в C # 4.0 конкретно помечает интерфейс как один или другой. С in
, вам разрешено помещать общий тип (обычно T) в позиции ввода , что означает аргументы метода и свойства только для записи.
С помощью out
вам разрешается помещать универсальный тип в выходные позиции, которые представляют собой возвращаемые значения метода, свойства только для чтения и параметры метода вывода.
Это позволит вам делать то, что намеревалось делать с кодом:
IEnumerable<Animal> animals = GetFishes();
List<T>
имеет как входящие, так и исходящие направления на T, поэтому он не является ни ковариантом, ни контравариантным, а интерфейсом, который позволял вам добавлять объекты, например:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
позволит вам сделать это:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Вот несколько видеороликов, которые демонстрируют концепции:
Вот пример:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Без этих отметок можно было бы скомпилировать следующее:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
или это:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants