为什么我可以通过索引访问KeyCollection / ValueCollection中的项目,即使它没有实现IList(Of Key)?

我注意到一个奇怪的VB.NET事情。 从这个问题来看,我提供了一种方法来通过索引来访问字典的KeysCollectionValuesCollection键和值,以获得第一项。 我知道它只在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没有实际的一个SortedDictionary(Of TKey, TValue).KeyCollection -class ,而且在ValueCollection也没有。 两者都实现ICollection ,它是IList的父接口。 所以你可以循环它并且它有一个Count属性,但你不能像我上面那样通过索引访问项目。

请注意,它是一个新的控制台应用程序,里面没有扩展。 我也不能去索引器的定义(也没有使用resharper)。 为什么它对我有用?

旁注:它在C#中不起作用。 我得到了预期的编译器错误:

无法将带有[]的索引应用于“SortedDictionary.KeyCollection”类型的表达式

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

这是编译VB.NET代码的屏幕截图:

在此处输入图像描述

它调用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(class [mscorlib]System.Collections.Generic.IEnumerable`1, int32) IL_0026: stloc.2 // firstKey 

Visual Basic语言规范 11.21.3中记录了此行为:

元素类型为T且尚未具有默认属性的每个可查询集合类型都被视为具有以下常规forms的默认属性:

 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成员,则会发生编译时错误。

使用Enumerable.ElementAtOrDefault或Enumerable.ElementAt时,性能成本相当高。 除非源实现IList(of T)接口,否则Linq没有更短的路由来到达指定索引处的元素。 因此它迭代每个元素,直到迭代计数达到指定索引的值。 没有魔法。 枚举。 Count()具有相同的故事,除非在这种情况下ICollection接口如果由source实现,Linq抓取它否则迭代,直到生成Count所需的最后一个元素。 我不知道为什么Vb.net会隐含地允许它,因为在我遇到严重的性能问题之前,这种情况很可能会被忽视。 Dictionary仅实现ICollection而不是IList。 我认为需要小心使用Vb.net,因为它不像C#那样是严格类型的语言。