foreach Dictionary<>.Values 或 foreach Dictionary<>
foreach Dictionary<>.Values or foreach Dictionary<>
我想知道在 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>
第二次迭代列表本身?
如果上面问题的答案是肯定的,那么第二种风格应该更有效,使第一种风格几乎无用,所以我认为我在某些方面应该是错误的。
我觉得这个问题应该在某个地方得到解答,但我找不到答案。
检索 Dictionary<,>
的 .Values
属性 是一个 O(1) 操作 (documented)。嵌套类型 Dictionary<,>.ValueCollection
是字典的简单包装器,因此在创建它时无需迭代。
调用 GetEnumerator()
时,您将获得嵌套的 Dictionary<,>.ValueCollection.Enumerator
结构的实例。它通过 Dictionary<,>
.
的 private
数组 entries
直接访问条目
您可以看到 source code.
所以您上面的 "Style one" 是一种很好且清晰的做事方式,没有性能开销。
请注意,获取值的顺序是任意的。您不知道底层数组 entries
是如何组织的,一旦 Dictionary<,>
在您开始 foreach
之前进行了多次插入和删除。
但是,"Style one" 和 "Style two" 得到的顺序是一样的;两者都以相同的方式访问 Dictionary<,>
的私有 entries
数组。
Values
不是在创建 List<T>
,不是。它甚至没有将整组值拉入一个单独的数据结构中。它所做的只是创建一个可以迭代值的枚举器。它所做的事情与直接迭代字典时发生的事情完全相同;不同之处在于,它不是为每对对象构造一个 KeyValuePair
对象,而是只给你一对中的一半。除此之外,迭代过程是一样的。
所有 3 种方法(Keys
、Values
和字典迭代)的行为相同 - 迭代字典中项目的内部集合。没有创建额外的 lists/arrays。
唯一的"extra"工作是在迭代开始后检查字典是否被修改(整数比较)。
您可以在 reference source
中查看确切的详细信息
在这两种情况下,您得到的都是 iterator 而不是临时集合。迭代器使用内部状态机来记住当前项目是什么,它使用 MoveNext 方法获取下一个项目。
如果您查看 IL 代码,您将看到 foreach 内部发生的更多详细信息。这是
的 IL
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
我想知道在 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>
第二次迭代列表本身?
如果上面问题的答案是肯定的,那么第二种风格应该更有效,使第一种风格几乎无用,所以我认为我在某些方面应该是错误的。
我觉得这个问题应该在某个地方得到解答,但我找不到答案。
检索 Dictionary<,>
的 .Values
属性 是一个 O(1) 操作 (documented)。嵌套类型 Dictionary<,>.ValueCollection
是字典的简单包装器,因此在创建它时无需迭代。
调用 GetEnumerator()
时,您将获得嵌套的 Dictionary<,>.ValueCollection.Enumerator
结构的实例。它通过 Dictionary<,>
.
private
数组 entries
直接访问条目
您可以看到 source code.
所以您上面的 "Style one" 是一种很好且清晰的做事方式,没有性能开销。
请注意,获取值的顺序是任意的。您不知道底层数组 entries
是如何组织的,一旦 Dictionary<,>
在您开始 foreach
之前进行了多次插入和删除。
但是,"Style one" 和 "Style two" 得到的顺序是一样的;两者都以相同的方式访问 Dictionary<,>
的私有 entries
数组。
Values
不是在创建 List<T>
,不是。它甚至没有将整组值拉入一个单独的数据结构中。它所做的只是创建一个可以迭代值的枚举器。它所做的事情与直接迭代字典时发生的事情完全相同;不同之处在于,它不是为每对对象构造一个 KeyValuePair
对象,而是只给你一对中的一半。除此之外,迭代过程是一样的。
所有 3 种方法(Keys
、Values
和字典迭代)的行为相同 - 迭代字典中项目的内部集合。没有创建额外的 lists/arrays。
唯一的"extra"工作是在迭代开始后检查字典是否被修改(整数比较)。
您可以在 reference source
中查看确切的详细信息在这两种情况下,您得到的都是 iterator 而不是临时集合。迭代器使用内部状态机来记住当前项目是什么,它使用 MoveNext 方法获取下一个项目。
如果您查看 IL 代码,您将看到 foreach 内部发生的更多详细信息。这是
的 ILvoid 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