Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I access an item in KeyCollection/ValueCollection by index even if it doesn't implement IList(Of Key)?

I've noticed a strange VB.NET thing. Coming from this question I've provided a way to access keys and values of dictionaries' KeysCollection and ValuesCollection via index to get, say, the first item. I know that it makes only sense in a SortedDictionary since a normal Dictionary is not ordered (well, you should not rely on its order).

Here's a simple example:

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)

But I was surprised that the question's asker said that it doesn't compile whereas it compiles and works for me without a problem:

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

So why can I use it like there was an indexer if there isn't actually one in SortedDictionary(Of TKey, TValue).KeyCollection-class and also none in the ValueCollection. Both implement ICollection<T> which is the parent interface of IList<T>. So you can loop it and it has a Count property, but you can't access items via index as I do above.

Note that it's a fresh console application with no extensions inside. I can't go to the definition of the indexer either(also not with resharper). Why does it work for me?

Side-note: it doesn't work in C#. I get the expected compiler error:

Cannot apply indexing with [] to an expression of type 'SortedDictionary.KeyCollection'

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

Here's a screenshot of the compiling VB.NET code:

enter image description here

like image 216
Tim Schmelter Avatar asked Apr 29 '16 08:04

Tim Schmelter


1 Answers

It invokes Enumerable.ElementAtOrDefault, not the indexer.

// [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

This behavior is documented in the Visual Basic Language Specification, 11.21.3:

Every queryable collection type whose element type is T and does not already have a default property is considered to have a default property of the following general form:

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

The default property can only be referred to using the default property access syntax; the default property cannot be referred to by name. For example:

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

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

If the collection type does not have an ElementAtOrDefault member, a compile-time error will occur.

like image 105
Cheng Chen Avatar answered Sep 25 '22 07:09

Cheng Chen