Я встречал их в учебнике по C #, который я читаю, но мне трудно их понять, вероятно, из-за отсутствия контекста.
Есть ли хорошее краткое объяснение того, что они из себя представляют и для чего они полезны?
Отредактируйте для пояснения:
Ковариантный интерфейс:
interface IBibble<out T>
.
.
Контравариантный интерфейс:
interface IBibble<in T>
.
.
c#
.net
interface
covariance
contravariance
NibblyPig
источник
источник
Ответы:
С помощью
<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) в позиции ввода , что означает аргументы метода и свойства только для записи.С помощью
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
источник
Этот пост - лучшее, что я читал по этой теме
Короче говоря, ковариация / контравариантность / инвариантность имеет дело с автоматическим приведением типов (от базового к производному и наоборот). Такое приведение типов возможно только в том случае, если соблюдаются некоторые гарантии в отношении действий чтения / записи, выполняемых над приведенными объектами. Прочтите сообщение для более подробной информации.
источник