ковариация дженериков и явное приведение - PullRequest
9 голосов
/ 06 декабря 2011

Если я попытаюсь сделать:

IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, List<string>>();

Я получаю ошибку:

ошибка CS0266: невозможно неявное преобразование типа 'System.Collections.Generic.Dictionary>' в 'System.Collections.Generic.IDictionary>'. Существует явное преобразование (вам не хватает приведения?)

Если я добавлю приведение:

IDictionary<uint, IEnumerable<string>> dict = (IDictionary<uint, IEnumerable<string>>)new Dictionary<uint, List<string>>();

Затем он компилируется.

Зачем мне нужно явное приведение? И это безопасно? Я думал, что весь смысл ковариации заключается в способности безопасно неявно разыгрывать заклинание?

EDIT: C # предотвращает несвязанное литье, например

string s = (string)0L;

ошибка CS0030: невозможно преобразовать тип 'long' в 'string'

Это позволяет явно понижать связанные типы, когда вы знаете, что объект на самом деле является подклассом:

Animal animal = new Cat();
Cat cat = (Cat)animal;

Я запутался, почему компилятор предлагает и позволяет мне явно приводить к IDictionary с несовместимыми типами.

Ответы [ 3 ]

8 голосов
/ 06 декабря 2011

IDictionary<TKey, TValue> не является ковариантным, ни для TKey, ни для TValue.

Ковариация будет означать, что IDictionary может создавать только типы TKey / TValue, но, поскольку он может производить, а также потреблять их, он не может быть ковариантным или контравариантным в этом отношении.

Я буду определять ковариацию / контравариантность в общих терминах;

IProducer<out T> является ковариантным, так что это означает, что он производит только T типов. Таким образом, когда вы передаете его ссылке на IProducer с более abstract T, приведение неявно, потому что верно следующее утверждение: «Производитель яблок - это производитель фруктов». (против "Производитель фруктов не обязательно производитель яблок")

IConsumer<in T> является контравариантным, что означает, что потребляет только T типов. Когда вы передаете ссылку на более конкретный T, приведение неявно, потому что верно следующее утверждение: «Потребитель фруктов - это потребитель яблок». (в отличие от «Потребитель яблок не обязательно является потребителем какого-либо фрукта»)

Что это означает в случае IDictionary, в частности, относительно TValue здесь:

У IDictionary есть методы, которые производят TValues, а также методы, которые используют TValues. Это, как говорится, означает, что оно не было (и не могло) быть объявлено ни ковариантным, ни контравариантным. (см. http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx - в определении общего интерфейса нет «out» или «in»)

Это означает, что когда вы пытаетесь неявно привести ваше Dictionary<uint, List<string>> в IDictionary<uint, IEnumerable<string>>, компилятор говорит: «Подождите, созданный вами объект может принять List<string> только в методе Add, но вы помещаете это в качестве ссылки, которая разрешит любое IEnumerable<string> in, которое является большим подмножеством. Если вы добавите что-либо, что является IEnumerable<string>, но не является List<string>, это не будет работать. " Он не позволяет (и не может) это косвенно допускать, поэтому вам нужен хард-каст.

(спасибо mquander за конкретный пример)

7 голосов
/ 06 декабря 2011

Это не безопасно. Например, теперь вы могли бы написать dict.Add(5, new string[0]), что взорвется, поскольку string[] не является List<string>. Тот факт, что это небезопасно, - вот почему вам нужен актерский состав.

Изменить для решения вашей обновленной проблемы:

C # разрешает любое явное приведение из любого ссылочного типа S к любому интерфейсу T («при ​​условии, что S не запечатан, а при условии, что S не реализует T».) Это поведение указано в разделе 6.2.4 спецификации языка. Так что это законно:

var foo = (IList<ICollection<IEnumerable<IntPtr>>>)new Uri(@"http://zombo.com");

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

5 голосов
/ 06 декабря 2011

Вы можете использовать

     IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, IEnumerable<string>>();

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

С учетом вышесказанного, вы можете использовать его как:

    dict.Add(1, new List<string>());

и т.д.

...