我想知道在C#中迭代Dictionary
集合的这两种样式的细节:
Dictionary<X, Y> xydic = new Dictionary<X, Y>();
第一种方式:
foreach (Y y in xydic.Values) { use y }
第二种方式:
foreach (var it in xydic) { Y y = it.Value; use y... }
我多年来一直是C ++开发人员(现在我在C#项目中工作)并且我不知道Dictionary
集合如何工作的细节,内存布局或者如何迭代元素,所以我想知道:
xydic.Values
会创建一个临时List<Y>
?我没有在the documentation中看到有关创建临时列表的任何信息。
如果创建临时列表,这意味着集合被迭代两次:首先创建List<Y>
,然后再迭代列表本身?
如果上面提到的问题的答案是肯定的,那么第二种风格应该更有效率,使第一种风格几乎无用,所以我认为我在某种程度上应该是错的。
我觉得这个问题应该在某处回答,但我找不到答案。
答案 0 :(得分:4)
检索.Values
的{{1}}属性是O(1)操作(documented)。嵌套类型Dictionary<,>
是字典周围的简单包装器,因此创建它时没有迭代。
在调用Dictionary<,>.ValueCollection
时,您将获得嵌套的嵌套GetEnumerator()
结构的实例。它直接通过Dictionary<,>.ValueCollection.Enumerator
的{{1}}数组private
来记录条目。
您可以看到source code。
所以你的&#34; Style one&#34;以上是一种良好而清晰的做事方式,没有性能开销。
请注意,获取值的顺序是任意的。一旦entries
在开始Dictionary<,>
之前进行了多次插入和删除,您就不知道底层数组entries
是如何组织的。
然而,你得到的顺序&#34;样式一&#34;和&#34;风格二&#34;是一样的;两者都以相同的方式访问Dictionary<,>
的私有foreach
数组。
答案 1 :(得分:2)
Values
没有创建List<T>
,没有。它甚至没有将整个值集拖入单独的数据结构中。它所做的只是创建一个可以迭代值的枚举器。它直接迭代字典时会发生完全相同的事情;不同之处在于,不是为每对对象构造一个KeyValuePair
对象,而是只给出对中的一半。除此之外,迭代过程是相同的。
答案 2 :(得分:0)
在这两种情况下,您都会获得iterator而不是临时收集。 Iterator使用内部状态机来记住当前项目是什么,并使用MoveNext方法获取nex项目。
如果你看一下IL代码,你会看到foreach内部的更多细节。这是IL for
void Main()
{
var dictionary = new Dictionary<int, string> { { 1, "one" }, { 2, "two" }};
foreach (var item in dictionary.Values)
{
Console.WriteLine(item);
}
}
IL代码:
IL_0000: nop
IL_0001: newobj System.Collections.Generic.Dictionary<System.Int32,System.String>..ctor
IL_0006: stloc.1
IL_0007: ldloc.1
IL_0008: ldc.i4.1
IL_0009: ldstr "one"
IL_000E: callvirt System.Collections.Generic.Dictionary<System.Int32,System.String>.Add
IL_0013: nop
IL_0014: ldloc.1
IL_0015: ldc.i4.2
IL_0016: ldstr "two"
IL_001B: callvirt System.Collections.Generic.Dictionary<System.Int32,System.String>.Add
IL_0020: nop
IL_0021: ldloc.1
IL_0022: stloc.0 // dictionary
IL_0023: nop
IL_0024: ldloc.0 // dictionary
IL_0025: callvirt System.Collections.Generic.Dictionary<System.Int32,System.String>.get_Values
IL_002A: callvirt System.Collections.Generic.Dictionary<System.Int32,System.String>+ValueCollection.GetEnumerator
IL_002F: stloc.2
IL_0030: br.s IL_0043
IL_0032: ldloca.s 02
IL_0034: call System.Collections.Generic.Dictionary<System.Int32,System.String>+ValueCollection+Enumerator.get_Current
IL_0039: stloc.3 // item
IL_003A: nop
IL_003B: ldloc.3 // item
IL_003C: call System.Console.WriteLine
IL_0041: nop
IL_0042: nop
IL_0043: ldloca.s 02
IL_0045: call System.Collections.Generic.Dictionary<System.Int32,System.String>+ValueCollection+Enumerator.MoveNext
IL_004A: brtrue.s IL_0032
IL_004C: leave.s IL_005D
IL_004E: ldloca.s 02
IL_0050: constrained. System.Collections.Generic.Dictionary<,>+ValueCollection.Enumerator
IL_0056: callvirt System.IDisposable.Dispose
IL_005B: nop
IL_005C: endfinally
IL_005D: ret