Проблема дисперсии C #: присвоение списка <Derived>как списка <Base> - PullRequest
52 голосов
/ 09 января 2010

Посмотрите на следующий пример (частично взят из MSDN Blog ):

class Animal { }
class Giraffe : Animal { }

static void Main(string[] args)
{
    // Array assignment works, but...
    Animal[] animals = new Giraffe[10]; 

    // implicit...
    List<Animal> animalsList = new List<Giraffe>();

    // ...and explicit casting fails
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>();
}

Это проблема ковариации? Будет ли это поддерживаться в будущем выпуске C #, и есть ли какие-нибудь умные обходные пути (используя только .NET 2.0)?

Ответы [ 4 ]

109 голосов
/ 09 января 2010

Ну, это конечно не будет поддерживаться в C # 4. Есть фундаментальная проблема:

List<Giraffe> giraffes = new List<Giraffe>();
giraffes.Add(new Giraffe());
List<Animal> animals = giraffes;
animals.Add(new Lion()); // Aargh!

Держите жирафов в безопасности: просто скажите "нет" небезопасной дисперсии.

Версия массива работает, потому что массивы do поддерживают дисперсию ссылочного типа с проверкой времени выполнения. Суть обобщения заключается в обеспечении безопасности времени компиляции .

В C # 4 будет поддерживаться safe универсальная дисперсия, но только для интерфейсов и делегатов. Таким образом, вы сможете сделать:

Func<string> stringFactory = () => "always return this string";
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4

Func<out T> является ковариантным в T, поскольку T используется только в выходной позиции. Сравните это с Action<in T>, который является контравариантным в T, потому что T используется только в позиции ввода, что делает это безопасным:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode());
Action<string> stringAction = objectAction; // Safe, allowed in C# 4

IEnumerable<out T> также является ковариантным, что делает это правильным в C # 4, как указывалось другими:

IEnumerable<Animal> animals = new List<Giraffe>();
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface.

С точки зрения решения этой проблемы в вашей ситуации в C # 2, вам нужно поддерживать один список, или вы были бы рады создать новый список? Если это приемлемо, List<T>.ConvertAll ваш друг.

16 голосов
/ 09 января 2010

Это будет работать в C # 4 для IEnumerable<T>, поэтому вы можете сделать:

IEnumerable<Animal> animals = new List<Giraffe>();

Однако List<T> не является ковариантной проекцией, поэтому вы не можете назначать списки, как вы делали выше, так какВы можете сделать это:

List<Animal> animals = new List<Giraffe>();
animals.Add(new Monkey());

Что явно недопустимо.

9 голосов
/ 09 января 2010

Что касается List<T>, боюсь, вам не повезло.Однако в .NET 4.0 / C # 4.0 добавлена ​​поддержка ковариантных / контравариантных интерфейсов.В частности, IEnumerable<T> теперь определяется как IEnumerable<out T>, что означает, что параметр типа теперь равен ковариант .

Это означает, что вы можете сделать что-то подобное вC # 4.0 ...

// implicit casting
IEnumerable<Animal> animalsList = new List<Giraffe>();

// explicit casting
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>();

Примечание. Типы массивов также были ковариантными (по крайней мере, начиная с .NET 1.1).

Мне кажется обидно, что поддержка дисперсии не была добавленаIList<T> и другие подобные универсальные интерфейсы (или даже универсальные классы), ну да, по крайней мере, у нас есть кое-что.

5 голосов
/ 09 января 2010

Ковариация / контравариантность не может поддерживаться в изменяемых коллекциях, как уже упоминали другие, поскольку невозможно гарантировать безопасность типов в обоих случаях во время компиляции; однако в C # 3.5 можно выполнить быстрое одностороннее преобразование, если это то, что вы ищете:

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = giraffes.Cast<Animal>().ToList();

Конечно, это не то же самое, это на самом деле не ковариация - вы фактически создаете другой список, но это, так сказать, "обходной путь".

В .NET 2.0 вы можете воспользоваться ковариацией массива для упрощения кода:

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = new List<Animal>(giraffes.ToArray());

Но учтите, что на самом деле вы создаете две новых коллекции здесь.

...