foreach Dictionary<> .Values or foreach Dictionary<>

时间:2015-09-09 14:42:57

标签: c# foreach iteration containers

我想知道在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>,然后再迭代列表本身?

如果上面提到的问题的答案是肯定的,那么第二种风格应该更有效率,使第一种风格几乎无用,所以我认为我在某种程度上应该是错的。

我觉得这个问题应该在某处回答,但我找不到答案。

3 个答案:

答案 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