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

18 浏览
0 Comments

为什么即使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-class中并没有索引器,ValueCollection中也没有。它们都实现了ICollection接口,这是IList的父接口。所以你可以循环它,并且它有一个Count属性,但是你不能像我上面那样通过索引访问项。

请注意,这是一个没有任何扩展的新的控制台应用程序。我也不能进入索引器的定义(甚至不能用resharper)。为什么它对我有效?

顺便说一句:在C#中它不起作用。我得到了预期的编译器错误:

Cannot apply indexing with [] to an expression of type

'SortedDictionary.KeyCollection'

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

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

enter image description here

0
0 Comments

这个问题的出现的原因是因为在KeyCollection/ValueCollection中,即使它们没有实现IList(Of Key),我们仍然可以通过索引访问其中的项。这是因为在访问项时,调用的是Enumerable.ElementAtOrDefault方法,而不是直接使用索引器。

解决方法是通过在KeyCollection/ValueCollection中实现ElementAtOrDefault方法来支持通过索引访问项。这样,即使它们没有实现IList(Of Key),仍然可以使用默认属性访问语法来访问项。

这个行为在Visual Basic语言规范中进行了说明。规范中指出,每个可查询的集合类型(其元素类型为T且没有默认属性)都被认为有一个默认属性,其形式如下:

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

这个默认属性只能使用默认属性访问语法来引用,不能通过名称来引用。如果集合类型没有ElementAtOrDefault成员,则会出现编译时错误。

这个解决方法在Visual Basic语言规范的11.21.3节中进行了详细说明。

原文链接:[Visual Basic Language Specification](https://msdn.microsoft.com/en-us/library/ms234437.aspx)

感谢Stephen Cleary在social.msdn.microsoft.com论坛上提供的信息。

参考链接:[social.msdn.microsoft.com/Forums/en-US/…](https://social.msdn.microsoft.com/Forums/en-US/36492946-8560-4b03-8607-4a47a2b569d2/no-default-property-for-linked-list-in-csharp-when-its-there-for-vb-?forum=netfxbcl)

0
0 Comments

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

在使用Enumerable.ElementAtOrDefault或Enumerable.ElementAt时,性能开销很大。除非源实现了IList(Of T)接口,否则Linq没有捷径来获取指定索引处的元素。因此,它会迭代每个元素,直到迭代次数达到指定索引的值为止。Enumerable.Count()也是同样的道理,除非源实现了ICollection接口,否则Linq会迭代直到需要产生Count的最后一个元素。

我不知道为什么Vb.net会隐式地允许这样,因为这样的情况很可能不会被注意到,直到我遇到严重的性能问题。Dictionary只实现了ICollection而不是IList。我认为在使用Vb.net时需要小心,因为它不像C#那样是严格类型的语言。

你是对的,这种自动实现的代码是性能问题的可怕源头。在大多数情况下,你可能都不会注意到它。但是如果它引起了性能问题,很难找到原因。但这与VB.NET是否严格类型化无关。即使在Option Strict On的情况下,你仍然会在集合类型上获得这个自动实现的索引器。唯一的避免方法是避免在不实现IList/IList(Of T)的类型上使用索引器。因此,你已经需要注意到这个"自动索引器"。

如果是这样的话,那么为什么选择C#而不是VB.net就真的引发了争议。

尽管这是一个潜在的性能问题,但它只在没有更快的方法可用时才会触发。在这里中,使用foreach返回第一个键 - 隐式的ElementAtOrDefault只会做这个,但是...隐式地。如果你必须从不支持它的集合中通过索引检索某个值,你最终会使用ElementAtOrDefault或编写一个概念上相同的方法。VB只是为你做这个,这是它的方式。

语言很可能做一些导致性能问题的事情,而不让你知道。更糟糕的是,索引器似乎非常高效,以至于每个人都使用它,甚至可能不将结果存储在变量中。这个问题的原因也是隐藏的。自动地提供索引器是一个非常糟糕的决定。

0