I want to know the details of this two styles of iterating Dictionary
collections in C#:
Dictionary xydic = new Dictionary
Retrieving the .Values
property of a Dictionary<,>
is an O(1) operation (documented). The nested type Dictionary<,>.ValueCollection
is a simple wrapper around the dictionary, so there is no iterating in creating it.
On calling GetEnumerator()
, you get an instance of the nested, nested Dictionary<,>.ValueCollection.Enumerator
struct. It acccesses the entries directly through the private
array entries
of the Dictionary<,>
.
You can see the source code.
So your "Style one" above is a good and clear way of doing things, with no performance overhead.
Note that the order in which you get the values, is arbitrary. You do not know how the underlying array entries
is organized, once the Dictionary<,>
has had many insertions and removals before you start foreach
ing it.
However, the order you get with "Style one" and "Style two" is the same; both access the private entries
array of the Dictionary<,>
in the same way.
In both cases you are getting iterator and not temporary collection. Iterator is using internal state machine to remember what is the current item, and it uses MoveNext method to get nex item.
If you look at IL code you will see more details what is going on inside of foreach. Here is the IL for
void Main()
{
var dictionary = new Dictionary<int, string> { { 1, "one" }, { 2, "two" }};
foreach (var item in dictionary.Values)
{
Console.WriteLine(item);
}
}
IL Code:
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
Values
is not creating a List<T>
, no. It's not even pulling the entire set of values into a separate data structure. All it's doing is creating an enumerator that can iterate over the values. It's doing exactly the same thing that happens when you iterate the dictionary directly; the difference is that instead of constructing a KeyValuePair
object for each pair of objects, its only giving you one half of the pair. Other than that, the iteration process is the same.
All 3 methods (Keys
, Values
and just iteration of dictionary) behave the same - iterate internal collection of items in the dictionary. There is no extra lists/arrays created.
The only "extra" work is to check if dictionary was modified after iteration is started (integer comparison).
You can check exact details in the reference source