Понимание ковариантных и контравариантных интерфейсов в C #

Я встретил их в учебнике, который я читаю на C #, но мне трудно понять их, возможно, из-за отсутствия контекста.

Есть ли хорошее краткое объяснение того, что они собой представляют и что они полезны для этого?

Изменить для уточнения:

Ковариантный интерфейс:

interface IBibble . . 

Контравариантный интерфейс:

 interface IBibble . . 

С помощью вы можете обрабатывать ссылку на интерфейс как одну вверху в иерархии.

С помощью вы можете обрабатывать ссылку интерфейса как одну вниз в hiearchy.

Позвольте мне объяснить это на более английском языке.

Предположим, вы получаете список животных из своего зоопарка, и вы намерены их обработать. Все животные (в вашем зоопарке) имеют имя и уникальный идентификатор. Некоторые животные – млекопитающие, некоторые – пресмыкающиеся, некоторые – земноводные, некоторые – рыба и т. Д., Но они все животные.

Итак, с вашим списком животных (который содержит животных разных типов), вы можете сказать, что у всех животных есть имя, поэтому, очевидно, было бы безопасно получить имя всех животных.

Однако, что, если у вас есть только список рыб, но нужно рассматривать их как животных, это работает? Интуитивно, он должен работать, но в C # 3.0 и ранее этот fragment кода не будет компилироваться:

 IEnumerable animals = GetFishes(); // returns IEnumerable 

Причиной этого является то, что компилятор не «знает» то, что вы намерены или можете делать с коллекцией животных после того, как вы его извлекли. Насколько известно, может существовать путь через IEnumerable чтобы вернуть объект в список, и это потенциально позволит помещать животное, которое не является рыбой, в коллекцию, которая должна содержать только рыба.

Другими словами, компилятор не может гарантировать, что это не разрешено:

 animals.Add(new Mammal("Zebra")); 

Поэтому компилятор просто отказывается компилировать ваш код. Это ковариация.

Давайте посмотрим на контравариантность.

Поскольку наш зоопарк может обрабатывать всех животных, он, безусловно, может обрабатывать рыбу, поэтому давайте попробуем добавить рыбу в наш зоопарк.

В C # 3.0 и ранее это не скомпилируется:

 List fishes = GetAccessToFishes(); // for some reason, returns List fishes.Add(new Fish("Guppy")); 

Здесь компилятор может разрешить эту часть кода, хотя метод возвращает List просто потому, что все рыбы являются животными, поэтому, если мы просто изменили типы на это:

 List fishes = GetAccessToFishes(); fishes.Add(new Fish("Guppy")); 

Тогда это сработает, но компилятор не может определить, что вы не пытаетесь это сделать:

 List fishes = GetAccessToFishes(); // for some reason, returns List Fish firstFist = fishes[0]; 

Поскольку список на самом деле является списком животных, это запрещено.

Таким образом, противоречие и коразмерность – это то, как вы обрабатываете ссылки на объекты и что вам разрешено делать с ними.

Входящие и out ключевые слова в C # 4.0 специально маркируют интерфейс как тот или другой. С помощью in , вы можете поместить общий тип (обычно T) во входные -позиции, что означает аргументы метода и свойства только для записи.

В out вы можете поместить общий тип в выходные -позиции, которые являются значениями возврата метода, свойствами только для чтения и параметрами метода out.

Это позволит вам делать то, что намеревается сделать с кодом:

 IEnumerable animals = GetFishes(); // returns IEnumerable // since we can only get animals *out* of the collection, every fish is an animal // so this is safe 

List имеет как входы, так и выходы на T, поэтому он не является ни ковариантным, ни контравариантным, а интерфейсом, позволяющим добавлять объекты, например:

 interface IWriteOnlyList { void Add(T value); } 

позволит вам сделать это:

 IWriteOnlyList fishes = GetWriteAccessToAnimals(); // still returns IWriteOnlyList fishes.Add(new Fish("Guppy")); <-- this is now safe 

Вот несколько видеороликов, которые показывают понятия:

  • Ковариация и контравариантность - VS2010 C # Часть 1 из 3
  • Ковариация и контравариантность - VS2010 C # Часть 2 из 3
  • Ковариация и контравариантность - VS2010 C # Часть 3 из 3

Вот пример:

 namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut { } interface IBibbleIn { } 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 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 d = GetInBase(); } static IBibbleOut GetOutDescendant() { return null; } static IBibbleIn GetInBase() { return null; } } } 

Без этих меток следующее компиляция:

 public List GetDescendants() ... List bases = GetDescendants(); bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant 

или это:

 public List GetBases() ... List descendants = GetBases(); <-- uh-oh, we try to treat all Bases as Descendants 

Этот пост – лучшее, что я прочитал по этому вопросу

Короче говоря, ковариация / контравариантность / инвариантность связана с автоматическим литьем типов (от базы к производной и наоборот). Эти типы типов возможны только в том случае, если некоторые гарантии соблюдаются в отношении действий чтения / записи, выполняемых на литых объектах. Читайте сообщение для более подробной информации.

Давайте будем гением компьютера.