С <out T>
вы можете рассматривать ссылку интерфейса как единицу вверх в иерархии.
С <in T>
вы можете рассматривать ссылку интерфейса как единицу вниз в иерархии.
Позвольте мне попытаться объяснить это в более английских терминах.
Допустим, вы извлекаете список животных из вашего зоопарка и намереваетесь их обработать. Все животные (в вашем зоопарке) имеют имя и уникальный идентификатор. Некоторые животные - млекопитающие, некоторые - рептилии, некоторые - амфибии, некоторые - рыбы и т. Д., Но все они животные.
Итак, с вашим списком животных (который содержит животных разных типов), вы можете сказать, что у всех животных есть имя, поэтому очевидно, что было бы безопасно получить имя всех животных.
Однако, что если у вас есть только список рыб, но вам нужно относиться к ним как к животным, это работает? Интуитивно понятно, что это должно работать, но в C # 3.0 и ранее этот фрагмент кода не будет компилироваться:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
Причина этого в том, что компилятор не «знает», что вы намереваетесь, или может делать с коллекцией животных после того, как вы ее получите. Насколько он знает, может быть способ через IEnumerable<T>
вернуть объект в список, и это потенциально позволит вам поместить животное, которое не является рыбой, в коллекцию, которая должна содержать только рыбу. .
Другими словами, компилятор не может гарантировать, что это не разрешено:
animals.Add(new Mammal("Zebra"));
Так что компилятор просто отказывается компилировать ваш код. Это ковариация.
Давайте посмотрим на контравариантность.
Поскольку наш зоопарк может обрабатывать всех животных, он, безусловно, может обрабатывать рыбу, поэтому давайте попробуем добавить немного рыбы в наш зоопарк.
В C # 3.0 и ранее это не компилируется:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));
Здесь компилятор может разрешить этот фрагмент кода, хотя метод возвращает List<Animal>
просто потому, что все рыбы - животные, поэтому, если мы просто изменили типы на это:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Тогда это будет работать, но компилятор не может определить, что вы не пытаетесь это сделать:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
Поскольку список фактически является списком животных, это не разрешено.
Таким образом, противоречивость и совпадение - это то, как вы относитесь к ссылкам на объекты и что вам разрешено делать с ними.
Ключевые слова in
и out
в C # 4.0 специально отмечают интерфейс как один или другой. С in
вы можете поместить универсальный тип (обычно T) в input -позиции, что означает аргументы метода и свойства только для записи.
При out
вы можете поместить универсальный тип в output -позициях, которые представляют собой возвращаемые значения метода, свойства только для чтения и параметры метода out.
Это позволит вам сделать то, что намеревался сделать с кодом:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe
List<T>
имеет как входное, так и выходное направления на T, поэтому он не является ни ко-вариантным, ни контра-вариантным, а интерфейсом, который позволяет вам добавлять объекты, например так:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
позволит вам сделать это:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
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)
{
// We can do this since every Descendant is also a Base
// and there is no chance we can put Base objects into
// the returned object, since T is "out"
// We can not, however, put Base objects into b, since all
// Base objects might not be Descendant.
IBibbleOut<Base> b = GetOutDescendant();
// We can do this since every Descendant is also a Base
// and we can now put Descendant objects into Base
// We can not, however, retrieve Descendant objects out
// of d, since all Base objects might not be Descendant
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