Почему я могу получить доступ к элементу в KeyCollection/ValueCollection по индексу, даже если он не реализует IList (Of Key)?

Я заметил странную вещь VB.NET. Исходя из этого вопроса Я предоставил способ доступа к ключам и значениям словарей KeysCollection и ValuesCollection через индекс, чтобы получить, скажем, первый элемент. Я знаю, что он имеет смысл только в SortedDictionary, так как нормальный Dictionary не упорядочен (ну, вы не должны полагаться на его порядок).

Вот простой пример:

Dim sortedDict As New SortedDictionary(Of DateTime, String)
sortedDict.Add(DateTime.Now, "Foo")

Dim keys As SortedDictionary(Of DateTime, String).KeyCollection = sortedDict.Keys
Dim values As SortedDictionary(Of DateTime, String).ValueCollection = sortedDict.Values
Dim firstkey As DateTime = keys(0)
Dim firstValue As String = values(0)

Но я был удивлен, что вопрос спросил, что он не компилируется, тогда как он компилируется и работает для меня без проблем:

System.Diagnostics.Debug.WriteLine("Key:{0} Value:{1}", firstkey, firstValue) ' Key:04/29/2016 10:15:23 Value:Foo

Итак, почему я могу использовать его, как есть индекс, если на самом деле нет в SortedDictionary(Of TKey, TValue).KeyCollection -class, а также нет в ValueCollection. Оба реализуют ICollection<T>, который является родительским интерфейсом IList<T>. Таким образом, вы можете закодировать его, и оно имеет свойство Count, но вы не можете получить доступ к элементам через индекс, как я выше.

Обратите внимание, что это новое консольное приложение без внутренних расширений. Я также не могу перейти к определению индексатора (также не с resharper). Почему это работает для меня?

Боковое примечание: в С# оно не работает. Я получаю ожидаемую ошибку компилятора:

Невозможно применить индексирование с [] к выражению типа 'SortedDictionary.KeyCollection'

var dict = new SortedDictionary<DateTime, string>();
dict.Add(DateTime.Now, "Foo");
DateTime dt = dict.Keys[0]; // here

Вот скриншот компиляции кода VB.NET:

введите описание изображения здесь

Ответ 1

Он вызывает Enumerable.ElementAtOrDefault, а не индексатор.

// [10 13 - 10 31]
IL_001f: ldloc.1      // keys
IL_0020: ldc.i4.0     
IL_0021: call         !!0/*valuetype [mscorlib]System.DateTime*/ [System.Core]System.Linq.Enumerable::ElementAtOrDefault<valuetype [mscorlib]System.DateTime>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0/*valuetype [mscorlib]System.DateTime*/>, int32)
IL_0026: stloc.2      // firstKey

Это поведение описано в Спецификация языка Visual Basic, 11.21.3:

Каждый тип запроса, тип элемента которого T и не имеет свойства по умолчанию, считается свойством по умолчанию следующего общего вида:

Public ReadOnly Default Property Item(index As Integer) As T
    Get
        Return Me.ElementAtOrDefault(index)
    End Get
End Property

Свойство по умолчанию можно ссылаться только на использование синтаксиса доступа к свойствам по умолчанию; свойство по умолчанию нельзя ссылаться по имени. Например:

Dim customers As IEnumerable(Of Customer) = ...
Dim customerThree = customers(2)

' Error, no such property
Dim customerFour = customers.Item(4)

Если тип коллекции не имеет члена ElementAtOrDefault, произойдет ошибка времени компиляции.

Ответ 2

Существует значительная стоимость исполнения, когда мы используем Enumerable.ElementAtOrDefault или Enumerable.ElementAt. Если источник не реализует интерфейс IList (из T), Linq не имеет более короткого маршрута для доступа к элементу по указанному индексу. Таким образом, он выполняет итерацию каждого элемента до тех пор, пока счетчик итераций не достигнет значения указанного индекса. Никакой магии. Перечислимых. Count() имеет ту же историю, что и в этом случае интерфейс ICollection, если он реализован по источнику, Linq захватывает его в противном случае, пока последний элемент не будет необходим для создания Count. Я не знаю, почему Vb.net допускает это неявно, потому что есть вероятность, что такие случаи останутся незамеченными до тех пор, пока я не столкнулся с серьезной проблемой производительности. Словарь реализует ICollection не IList. Я думаю, что нужно быть осторожным с Vb.net, поскольку он не является строго типизированным языком как С#.