C# example

How to iterate over a dictionary in C#

8 min read Updated Oct 2026 Runs in an isolated runtime
Quick answer

Use foreach over the dictionary itself: foreach (var (key, value) in dict). Each item is a KeyValuePair<TKey, TValue>, so the older form foreach (var pair in dict) with pair.Key and pair.Value works too. To change a value, assign through the indexer: dict[key] = newValue.

Dictionary<TKey, TValue> is enumerable, so a plain foreach visits every entry, and its Keys and Values properties let you loop over just one side. This page covers each of those, why looping over the keys and indexing back into the dictionary costs more than it looks, how to update values (including the increment-a-counter case), what you may and may not change inside the loop, the order you get entries in, and how to merge one dictionary into another. Each example runs on this page: hit Run, then edit the code and run it again.

1foreach over KeyValuePairRecommended

Enumerating a Dictionary yields one KeyValuePair<TKey, TValue> per entry, with Key and Value properties. Write the type out, or let var infer it. Since .NET Core 2.0, KeyValuePair also has a Deconstruct method, so foreach (var (key, value) in dict) gives you two named variables straight away. It is an ordinary foreach, so break, continue, return and await all behave as usual.

Program.cs

Output

Prints apple -> 12, banana -> 0, cherry -> 7, then units: 19, then the deconstructed loop as an aligned table ({fruit,-7} pads the name, {count,3} right-aligns the number). stock.First() prints [apple, 12], and string.Join gives [apple, 12], [banana, 0], [cherry, 7]. These entries come out in the order they were added because nothing has been removed yet, but Dictionary doesn't promise any order; section 5 shows when it changes. The deconstruction form needs .NET Core 2.0 or later; on .NET Framework, use pair.Key and pair.Value.

2Keys and Values, and why Keys + dict[key] is wasteful

When you need only one side, loop over dict.Keys or dict.Values. Both are live views of the dictionary, not copies, and both work with LINQ (stock.Values.Sum()). The pattern to avoid is looping over Keys and reading dict[key] inside: every indexer call hashes the key and searches for the entry the loop was already standing on. The snippet passes a comparer that counts hash calls to make the difference visible.

Program.cs

Output

The keys print as fruit: apple, fruit: banana, fruit: cherry, then units: 19, values: [12, 0, 7]. Both totals are 19, but the lookup counts differ: Keys + stock[key]: total 19, lookups 3 against foreach pair: total 19, lookups 0. The _ discards the key in the deconstruction when you only want the value. For a single key that may be missing, TryGetValue does one lookup (cherry: 7, lookups 1), where ContainsKey followed by the indexer does two.

3Update a value in a dictionary

dict[key] = value is the update: it overwrites the value if the key exists and adds the key if it doesn't. Add is stricter and throws on a duplicate key, and TryAdd (.NET Core 2.0+) returns false instead. The common trap is counts[word]++ on a key that isn't there yet, because the read half of ++ throws. GetValueOrDefault fixes that, and on .NET 6+ CollectionsMarshal.GetValueRefOrAddDefault does it with one lookup instead of two.

Program.cs

Output

The indexer gives [tea, 2.50], [coffee, 3.20], [juice, 4.00]. Add throws ArgumentException: An item with the same key has already been added. Key: tea, and TryAdd prints TryAdd tea: False, tea is still 2.50. The bare ++ throws KeyNotFoundException: The given key 'to' was not present in the dictionary., and both counting loops print [to, 2], [be, 2], [or, 1], [not, 1]. The last two loops change every price: [tea, 2.75], [coffee, 3.52], [juice, 4.40], then [tea, 2.50], [coffee, 3.27], [juice, 4.15]. Overwriting an existing key inside a foreach works on current .NET, but the documentation only promises that Remove and Clear leave the enumerator valid, and on .NET Framework any change throws. Looping over Keys.ToList() is the version that is safe everywhere. You can't assign pair.Value: KeyValuePair is read-only, so it won't compile.

4Removing entries while iterating

Since .NET Core 3.0, Dictionary<TKey, TValue>.Remove inside a foreach over the same dictionary is allowed. Adding a new key is not: the next step of the loop throws InvalidOperationException. If your code also targets .NET Framework, where Remove in the loop throws too, collect the keys first and remove them afterwards, or build a filtered copy with LINQ.

Program.cs

Output

Removing in the loop prints remove in loop: [alice, 90], [carol, 72]. Adding a key throws InvalidOperationException: Collection was modified; enumeration operation may not execute., and entries now: 5 shows the worse part: the first new key went in before the exception, so the dictionary is left half-changed. Collecting the keys first prints collect, remove: [alice, 90], [carol, 72] (dropped bob, dana), and the filtered copy is ToDictionary(): [alice, 90], [carol, 72]. The parameterless ToDictionary() on a sequence of pairs is .NET 8+; on older versions write .ToDictionary(pair => pair.Key, pair => pair.Value).

5Iteration order: Dictionary, SortedDictionary, OrderedDictionary

The documentation says the order of a Dictionary is undefined. In practice a dictionary that has only ever had keys added enumerates in insertion order, which is why so much code quietly depends on it, until a Remove frees a slot and the next new key fills the gap. When order matters, sort while iterating with OrderBy, or pick a type that defines one: SortedDictionary keeps keys sorted, and .NET 9 added the generic OrderedDictionary<TKey, TValue> for insertion order with indexes.

Program.cs

Output

Prints Dictionary: http, https, ssh, then after Remove, Add: dns, https, ssh: dns was added last but comes out first, in the slot http left. The OrderBy loop prints dns = 53, https = 443, ssh = 22, and sorting by value gives [https, 443], [dns, 53], [ssh, 22]. The SortedDictionary puts the new key in its place: dns, ftp, https, ssh. The OrderedDictionary keeps the order you built: ftp, ssh, dns, with GetAt(0) returning [ftp, 21]. Before .NET 9, the only OrderedDictionary was the non-generic one in System.Collections.Specialized, which stores object keys and values.

6Merge two dictionaries

There is no built-in Merge, so merging is a loop over the second dictionary. What decides the code is the rule for keys that appear in both: the indexer lets the second one win, TryAdd keeps the first, and Concat + ToDictionary throws. For any other rule, or more than two dictionaries, flatten them with SelectMany and GroupBy the key.

Program.cs

Output

The indexer merge prints indexer: [theme, dark], [lang, en], [font, mono], [zoom, 125%], and the TryAdd merge keeps [theme, light] with the same other keys. Concat + ToDictionary throws ArgumentException: An item with the same key has already been added. Key: theme, so use it only when the keys can't overlap. The grouped version sums duplicates: summed: [north, 11], [south, 10], [east, 3]. Copying with new Dictionary<string, string>(defaults) first leaves both inputs unchanged; drop the copy to merge into defaults in place.

7Which should you use?

MethodGives youLookups per entryBest for
foreach (var (k, v) in dict)Key and valueNoneThe default loop, .NET Core 2.0+
foreach (var pair in dict)KeyValuePairNoneAny version, including .NET Framework
foreach (var k in dict.Keys)Keys onlyNoneWhen you never need the value
dict.ValuesValues onlyNoneSums, counts, scans over values
Keys + dict[key]Key and valueOne extraAvoid: loop over the pairs instead
dict.OrderBy(p => p.Key)A defined orderNone, plus a sortPrinting, reports, stable output
Keys.ToList() / Where(…).ToList()A copy to loop overOne per changeChanging the dictionary on any .NET version

Frequently asked questions

What is the best way to iterate over a dictionary in C#?

A foreach over the dictionary itself: foreach (var (key, value) in dict) on .NET Core 2.0+, or foreach (var pair in dict) with pair.Key and pair.Value on any version. Each entry is visited once and its value comes with it. If you only need one side, loop over dict.Keys or dict.Values. Avoid looping over Keys and reading dict[key] inside, which does an extra hash lookup per entry.

How do I update a value in a Dictionary?

Assign through the indexer: dict[key] = newValue overwrites the value, or adds the key if it is missing. For counters, counts[key]++ throws KeyNotFoundException when the key is new, so write counts[key] = counts.GetValueOrDefault(key) + 1. You cannot assign pair.Value inside a foreach, because KeyValuePair is read-only: the compiler reports error CS0200, "Property or indexer 'KeyValuePair<string, int>.Value' cannot be assigned to -- it is read only".

Why do I get "Collection was modified; enumeration operation may not execute"?

Because the dictionary changed in a way its enumerator does not allow while the foreach was running. On .NET Core 3.0 and later, Remove and Clear are allowed during the loop, and overwriting an existing key works on current .NET, but adding a new key throws InvalidOperationException. On .NET Framework every change throws. The fix that works everywhere is to loop over a copy, such as foreach (var key in dict.Keys.ToList()), or to collect the keys to change first and apply the changes after the loop.

Does a C# Dictionary keep insertion order?

Not by contract. The documentation leaves the order undefined. A dictionary that has only had keys added usually enumerates in insertion order, but after a Remove the next new key can fill the freed slot and come out earlier. Use dict.OrderBy(p => p.Key) when you need a sorted walk, SortedDictionary<TKey, TValue> to keep keys sorted all the time, or OrderedDictionary<TKey, TValue> (.NET 9+) to keep insertion order.

How do I merge two dictionaries in C#?

Loop over the second one and assign with the indexer, foreach (var (k, v) in second) first[k] = v;, so duplicate keys take the second value. Use first.TryAdd(k, v) in the loop to keep the first value instead. first.Concat(second).ToDictionary() throws ArgumentException on a duplicate key. For a one-liner where the last value wins, group first: a.Concat(b).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value).

How do I get a dictionary entry by index?

dict.ElementAt(1) returns the second KeyValuePair the enumerator yields, such as [b, 2], but it walks the entries to get there and the order is undefined, so it is rarely what you want. If you need positions, use OrderedDictionary<TKey, TValue> on .NET 9+: GetAt(index) returns the pair at that position and IndexOf(key) returns a key's position.

Why does Console.WriteLine(dict) print the type name instead of the contents?

Dictionary does not override ToString(), so you get its full type name, which starts with System.Collections.Generic.Dictionary and ends with [System.String,System.Int32] for a Dictionary<string, int>. Each KeyValuePair does format itself as [key, value], so string.Join(", ", dict) prints the whole dictionary as [a, 1], [b, 2], [c, 3].

Run it yourself

Open any of these in the full C# editor: tweak, run, and share.

C# playground