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:
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.
There is a considerable performance cost when we use Enumerable.ElementAtOrDefault or Enumerable.ElementAt.
Unless the source implements IList(of T) interface, Linq does not have shorter route to get to the element at the specified index. So it iterates each element untill iteration count reaches to value of specified index. No magic. Enumerable. Count() has the same story except in this case ICollection interface if implemented by source, Linq grabs it otherwise iteration untill the last element is required to produce Count.
I don't know why Vb.net allowing it implicitly because chances are that cases like this will remain unnoticed until I face serious performance issue. Dictionary only implements ICollection not IList. I think one needs to be careful with Vb.net as it is not as strictly typed language as C#.