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

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

Существует ли хорошее краткое объяснение того, что они и для чего они полезны там?

Редактирование для разъяснения:

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

interface IBibble<out T>
.
.

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

interface IBibble<in T>
.
.
82
задан Henk Holterman 27 July 2015 в 09:17
поделиться

1 ответ

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

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

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

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

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

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

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

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

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

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

Таким образом, компилятор просто отказывается компилировать ваш код. Это ковариация.

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

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

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

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

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

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 специально помечают интерфейс как тот или иной. С в вам разрешено помещать общий тип (обычно T) в input -positions, что означает аргументы метода и свойства только для записи.

С 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, поэтому он не является ни ковариант, ни контравариант, но интерфейс, позволяющий добавлять объекты, например:

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
137
ответ дан 24 November 2019 в 09:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: