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



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



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



редактировать для уточнения:



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



interface IBibble<out T>
.
.


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



interface IBibble<in T>
.
.
645   2  

2 ответов:

С <out T>, вы можете рассматривать ссылку на интерфейс как один вверх в иерархии.

С <in T>, вы можете рассматривать ссылку на интерфейс как один вниз в hiearchy.

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

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

однако, что если у вас есть список только рыбы, но нужно относиться к ним как к животным, это работает? Интуитивно, он должен работать, но в 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];

С список список животных, это не допускается.

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

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

С out, вы можете разместить универсальный тип в выход - позиции, которые являются возвращаемыми значениями метода, свойствами только для чтения и параметрами метода 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

этот пост это лучшее, что я читал на эту тему

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

Comments

    Ничего не найдено.