Tim Schmelter Tim Schmelter - 7 months ago 11
Vb.net Question

Why can i access an item in KeyCollectio/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 f.e. 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 it's 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 suprised that OP 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

Answer

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.