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

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

我注意到一个奇怪的vb.net东西。从这个问题出发,我提供了一种通过索引访问字典的KeysCollectionValuesCollection的键和值的方法,比如说,获取第一项。我知道在一个SortedDictionary中这是有意义的,因为一个正常的Dictionary没有被订购(好吧,你不应该依赖它的订单)。

下面是一个简单的例子:

1
2
3
4
5
6
7
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)

但令我惊讶的是,问题的提问者说它不编译,而它编译并为我工作却没有问题:

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

所以,如果SortedDictionary(Of?TKey,?TValue).KeyCollection类中没有索引器,而ValueCollection类中也没有索引器,为什么我可以像使用索引器一样使用它呢?两者都实现了ICollection,这是IList的父接口。所以你可以循环它,它有一个Count属性,但是你不能像我上面所做的那样通过索引访问项目。

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

旁注:它不适用于C。我得到预期的编译器错误:

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

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

下面是编译vb.net代码的屏幕截图:

enter image description here


它调用Enumerable.ElementAtOrDefault,而不是索引器。

1
2
3
4
5
// [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

此行为记录在Visual Basic语言规范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:

1
2
3
4
5
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:

1
2
3
4
5
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.


当我们使用Enumerable.elementorDefault或Enumerable.elementat时,会有相当大的性能成本。除非源实现了ilist(of t)接口,否则linq没有较短的路径来到达指定索引处的元素。所以它迭代每个元素,直到迭代计数达到指定索引的值。没有魔法。可枚举。count()具有相同的情况,但在本例中,ICollection接口(如果由源实现)除外,Linq将获取它,否则将进行迭代,直到需要最后一个元素来生成count为止。我不知道为什么vb.net会隐式地允许它,因为在我面临严重的性能问题之前,这样的情况很可能不会被注意到。字典只实现ICollection而不是IList。我认为人们需要小心使用vb.net,因为它不像C那样严格地输入语言。