2016-07-17 2 views
1

Я столкнулся с ситуацией, когда тот факт, что List.AsReadOnly() возвращает ReadOnlyCollection вместо IReadOnlyCollection, усложнил для меня все. Поскольку возвращенная коллекция была ValueDictionary, она не может быть автоматически повышена до IReadOnlyCollection. Это показалось странным, и после обзора исходного кода .Net я подтвердил, что метод делает что-то другое для List, чем для Dictionary, а именно, возвращает конкретный класс вместо интерфейса.Почему List.AsReadOnly возвращает ReadOnlyCollection, но Dictionary.AsReadOnly возвращает IReadOnlyDictionary?

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

В моем коде я сначала подумал, что, поскольку мой потребитель был только частным методом, я мог бы изменить его сигнатуру параметра от IReadOnlyDictionary<T, IReadOnlyCollection<T>> до IReadOnlyDictionary<T, ReadOnlyCollection<T>>. Но потом я понял, что это делает его похожим на частный метод может изменять значение сбора, так что я положил раздражающее явное приведение в ранее коду, чтобы правильно использовать интерфейс:

.ToDictionary(
    item => item, 
    item => (IReadOnlyCollection<T>) relatedItemsSelector(item) 
     .ToList() 
     .AsReadOnly() // Didn't expect to need the direct cast 
) 

О, и так как Я всегда сбиваюсь с ковариацией и контравариантностью, может кто-нибудь, пожалуйста, скажите мне, какой из них препятствует автоматическому броску, и попытайтесь напомнить мне разумным образом, как запомнить их на будущее? (например, коллекции не ______variant [co/contra] для параметров _____ [ввода/вывода].) Я понимаю, почему этого не может быть, потому что может быть много реализаций интерфейса, и небезопасно отображать все отдельные элементы словаря запрашиваемого типа. Если я не вдуваю даже этот простой аспект, и я не знаю, как это сделать, а я не знаю поймите его, и в этом случае я надеюсь, что вы сможете помочь мне правильно ...

ответ

0

Причина в историческом. В .NET 4.5 добавлены интерфейсы IReadOnly *, а в .NET 2.0 добавлен List<T>.AsReadOnly(). Изменение его типа возврата было бы изменением.

Явное приведение не так уж плохо. Это даже не время выполнения, так как компилятор может статически проверять его (ни один из них не испускается в IL). Кстати, вы можете отдать его IReadOnlyList<T>, который также предлагает индексированный доступ к списку. Вы также можете написать метод расширения, который возвращает нужный вам тип (например, AsReadOnlyList()).

Что касается разнообразия {co, contra}, мне легче запомнить ключевые слова C# in (контравариантный) и out (ковариант). in параметры типа могут отображаться только как ввод аргументы метода, а параметры типа out могут отображаться только как вывод (возвращаемые значения). Способ, который принимает параметр, например. типа Base, безопасно называться с типом Derived, поэтому безопасно отливать параметры in в этом направлении. out - это как раз наоборот.

Например:

interface IIn<in T> { Set(T value); } 
IIn<Base> b = ... 
IIn<Derived> d = b; 
d.Set(derived); // safe since any method accepting Base can handle Derived 

interface IOut<out T> { T Get(); } 
IOut<Derived> d = ... 
IOut<Base> b = d; 
b.Get(); // safe since any Derived is Base 

Non-только для чтения интерфейсы сбора не может быть * -вариант, так как они должны быть как in и out, и это было бы небезопасно. Компилятор и CLR этого не допускают. ,NET имеет форму небезопасной дисперсии с массивами:

var a = new[] { "s" }; 
var o = (object[])a; 
o[0] = 1; // ArrayTypeMismatchException 

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

+0

О, о, о! Я предполагал, что он не будет автоматически отбрасываться, потому что он * не был ковариантным, но теперь я вижу, что именно потому, что 'IReadOnlyCollection' читается только, компилятор может видеть, что он безопасно отбрасывать из производного типа в базу , Так что, если это так, почему я должен сделать явный бросок? – ErikE

+0

Ковариация и контравариантность выполняются только при использовании двух общих * интерфейсов * и изменении параметра * типа * (от базового до производного или наоборот). Листинг, который вы использовали, - это просто бросок - это разрешено, потому что 'ReadOnlyCollection ' реализует 'IReadOnlyList '. –

+0

О, упс, это имеет смысл. Это становится яснее. Почему он не может быть неявно брошен? – ErikE

Смежные вопросы