Какой смысл в этом ковариационном коде? - PullRequest
2 голосов
/ 23 марта 2020

Я читал книгу, названную c# 7.0 в двух словах О'РЕИЛЛИ , Топи c: Дисперсия не является автоматом c. Есть пример с 2 классами, Животное и Медведь, в котором Животное> Медведь:

public class Animal { }
public class Bear: Animal { }

А также есть такой класс:

public class Stack<T>
{
    private int position;
    T[] data = new T[100];
    public void Push(T obj) => data[position++] = obj;
    public T Pop() => data[--position];
}

В продолжении есть 2 версии того же класса:

public class ZooCleaner1
{
    public static void Wash(Stack<Animal> animals) { }
}

и:

public class ZooCleaner2
{
    public static void Wash<T>(Stack<T> animals) where T: Animal { }
}

Это объясняет, что если я пытаюсь написать:

ZooCleaner1.Wash(bears);
ZooCleaner2.Wash(bears);

, первая строка получает время компиляции ошибка, которая говорит о том, что он не может конвертировать Bear в Animal. Но вторая строка правильная и работает нормально. Поскольку я новичок в этом топе c, я не могу понять разницу между этими двумя строками, и я думаю, что они оба принимают Stack<Animal>, и почему мы должны использовать условные генерики?

Ответы [ 2 ]

3 голосов
/ 23 марта 2020

Stack<Animal> представляет собой стек объектов любого типа Animal. Stack<T> where T: Animal представляет стек типа single , если этот тип наследуется от Animal.

. Вы не можете использовать Stack<Bear> вместо параметра, объявленного как Stack<Animal> потому что, если вы могли бы , то метод мог бы положить sh a Fish на стопку медведей. Когда метод, использующий стек Bear s, выталкивает его из стека, представьте себе сюрприз, когда он выталкивает файл sh!

Второй метод, с другой стороны, это generi c, что означает, что он может принимать стек любого типа, если этот тип наследуется от Animal Так что, если метод получает Stack<Bear>, он может только pu sh еще Bear в стек. Попытка pu sh a Fish будет ошибкой во время выполнения.

1 голос
/ 23 марта 2020

Я бы не назвал это "ковариацией". Это - общая c дисперсия. Ваш код демонстрирует только общие ограничения c.

Давайте посмотрим, что мы можем сделать в каждом из методов Wash. В первом методе Wash мы можем:

public static void Wash(Stack<Animal> animals) { 
    animals.Push(new Animal());
    Animal a = animals.Pop();
}

Теперь предположим, что у вас есть Stack<Bear> bears;, и вы хотите передать его в первый Wash. Вы видите, как это создаст противоречие, если компилятор позволил вам это сделать? Вы не можете добавить Animal к Stack<Bear>! Но что касается Wash, то добавление Animal вполне нормально, потому что известно только то, что он может принять Stack<Animal>!

Следовательно, Stack<Bear> не является подтипом Stack<Animal>, поскольку вы не можете добавить Animal s к первому, но вы можете добавить ко второму.

Во втором методе Wash, хотя вы можете передать ему bears, вы не можете добавить Animal s до ti:

public static void Wash<T>(Stack<T> animals) where T: Animal { 
    animals.Push(new Animal()); // error
    Animal a = animals.Pop();
}

Поскольку компилятор не уверен, что Stack<T> является Stack<Animal>. Это может быть , но также может быть Stack<Bear> или Stack<Unicorn> или Stack<SomeOtherSubclassOfAnimal>, верно?

...