Понимание ковариантного и контравариантного интерфейсов в C # - PullRequest
81 голосов
/ 27 апреля 2010

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

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

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

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

interface IBibble<out T>
.
.

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

interface IBibble<in T>
.
.

Ответы [ 2 ]

134 голосов
/ 27 апреля 2010

С <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
6 голосов
/ 04 января 2015

Этот пост - лучшее, что я читал по теме

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...