Понимание Ковариантных и контравариантных интерфейсов в языке Си#
Я столкнулся с ними в учебнике, который читаю на C#, но мне трудно понять их, вероятно, из-за отсутствия контекста.
Есть ли хорошее краткое объяснение того, что они собой представляют и для чего они полезны?Изменить для уточнения:
Ковариантный интерфейс:
interface IBibble<out T>
.
.
Контравариантный интерфейс:
interface IBibble<in T>
.
.
2 ответа:
С помощью
<out T>
можно рассматривать ссылку на интерфейс как одну из вершин иерархии.С помощью
Позвольте мне попытаться объяснить это более английскими терминами. Предположим, вы извлекаете список животных из своего зоопарка и собираетесь обработать их. Все животные (в вашем зоопарке)имеют имя и уникальный идентификатор. Некоторые животные-млекопитающие, некоторые-рептилии, некоторые-амфибии, некоторые-рыбы, и т.д. но все они-животные. Итак, с вашим списком животных (который содержит животных разных типов), вы можете сказать, что все животные имеют имя, поэтому, очевидно, было бы безопасно получить имя всех животных. Однако, что делать, если у вас есть список только рыб, но нужно относиться к ним как к животным, это работает? Интуитивно это должно работать, но в C# 3.0 и раньше этот фрагмент кода не будет компилироваться:<in T>
, вы можете рассматривать ссылку на интерфейс как один вниз в hiearchy.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-positions, то есть возвращаемые значения метода, свойства только для чтения и параметры метода 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
Вот несколько видео, которые показывают концепции:
- ковариация и контравариантность-VS2010 C# Часть 1 из 3
- ковариация и контравариантность-VS2010 C# Часть 2 из 3
- ковариация и контравариантность-VS2010 C# Часть 3 из 3
Вот пример:
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
Этот пост - лучшее, что я читал на эту тему
Короче говоря, ковариация / контравариация / инвариантность имеет дело с автоматическим приведением типов (от базового к производному и наоборот). Приведение этих типов возможно только при соблюдении некоторых гарантий в отношении операций чтения / записи, выполняемых над приведенными объектами. Читайте Сообщение для получения более подробной информации.