Dictionary allows us to store items in a collection using key-value pairs. It is included in the System.Collection.Generic namespace. In this article, we are going to learn how to iterate through a Dictionary in different ways.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Using the Foreach Loop

Let’s define a Dictionary object that we are going to use throughout the article:

var monthsInYear = new Dictionary<int, string>();

The simplest method to go through the values of this Dictionary is using a foreach loop. First, let’s initialize our Dictionary with some values:

var monthsInYear = new Dictionary<int, string>
{
    {1, "January" },
    {2, "February" },
    {3, "March" },
    {4, "April" }
};

Next, let’s use this Dictionary to loop through all the values with a foreach loop:

public static void SubDictionaryUsingForEach(Dictionary<int,string> monthsInYear)
{
    foreach (var month in monthsInYear)
    {
        Console.WriteLine($"{month.Key}: {month.Value}");
    }
}

We can also implement foreach loop using a KeyValuePair<TKey,TValue> structure. We can retrieve multiple fields from an object in a single operation (deconstruction) by assigning the selected values to individual variables. Then, we can use a type interface when deconstructing inside a foreach loop, which lets us unpack key and value in a single operation.

There are several ways to utilize this.

We can either get only the individual key({month number}) , and value({month name}) items or access them as a pair:

public static void SubDictionaryKeyValuePair(Dictionary<int,string> monthsInYear)
{
    foreach (KeyValuePair<int,string> entry in monthsInYear)
    {
        Console.WriteLine($"{entry.Key}: {entry.Value}");
    }

    foreach (var (key,value) in monthsInYear)
    {
        Console.WriteLine($"{key}: {value}");   
    }
}

Using the For Loop

The next method is a for loop to iterate over a Dictionary. In this, to get KeyValuePair<TKey,TValue> at a specified index in a sequence, we use the ElementAt() method. Thus for every entry in the for loop, at the specified index, we can perform any required operation:

public static void SubDictionaryForLoop(Dictionary<int, string> monthsInYear)
{
   for (int index = 0; index < monthsInYear.Count; index++)
   {
       KeyValuePair<int, string> month = monthsInYear.ElementAt(index);

       Console.WriteLine($"{month.Key}: {month.Value}");
   }
}

ParallelEnumerable.ForAll() Method

There is another way we can iterate through the dictionary, and that’s with the parallel ForAll() method. We’re going to introduce this method because it might be fun to see how it performs with larger dictionaries in comparison to the for and foreach loops.

This method incorporates parallel processing of key-value pairs in the Dictionary:

public static void SubDictionaryParallelEnumerable(Dictionary<int, string> monthsInYear)
{
    monthsInYear.AsParallel()
                .ForAll(month => Console.WriteLine($"{month.Key} : {month.Value}"));
}

The results might not be sorted, but it does loop through the dictionary.

For vs Foreach vs ParallelEnumerable.ForAll()

We can implement a performance benchmark to show the differences between these methods. We’re going to remove the Console.Writeline() and do the simple assignment:

[Benchmark]
[ArgumentsSource(nameof(SampleData))]
public void WhenDictionaryUsingForEach(Dictionary<int, string> dictionaryData, string numberOfItems)
{
    foreach (var testValue in dictionaryData)
    {
        var result = testValue.Value;
    }
}

[Benchmark]
[ArgumentsSource(nameof(SampleData))]
public void WhenDictionaryUsingForLoop(Dictionary<int, string> dictionaryData, string numberOfItems)
{
    for (int i = 0; i < dictionaryData.Count; i++)
    {
        var item = dictionaryData.ElementAt(i);
        var result = item.Value;
    }
}

[Benchmark]
[ArgumentsSource(nameof(SampleData))]
public void WhenDictionaryParallelEnumerable(Dictionary<int, string> dictionaryData, string numberOfItems)
{
    var result = string.Empty;

    dictionaryData.AsParallel().ForAll(testValue => result = testValue.Value);
}

We’re going to test for three different scenarios or rather with the different number of elements in the dictionary: 100, 1000, and 10000:

|                           Method | numberOfItems |             Mean |           Error |          StdDev |
|--------------------------------- |-------------- |-----------------:|----------------:|----------------:|
|       WhenDictionaryUsingForEach |           100 |         381.8 ns |         7.65 ns |        15.80 ns |
|       WhenDictionaryUsingForLoop |           100 |      35,978.4 ns |       569.42 ns |       609.27 ns |
| WhenDictionaryParallelEnumerable |           100 |      48,180.4 ns |       952.48 ns |     2,282.08 ns |
|       WhenDictionaryUsingForEach |          1000 |       3,667.2 ns |        59.86 ns |        56.00 ns |
|       WhenDictionaryUsingForLoop |          1000 |   3,075,311.3 ns |    39,083.23 ns |    36,558.47 ns |
| WhenDictionaryParallelEnumerable |          1000 |     138,527.8 ns |     2,617.68 ns |     2,570.91 ns |
|       WhenDictionaryUsingForEach |         10000 |      35,489.4 ns |       412.75 ns |       365.89 ns |
|       WhenDictionaryUsingForLoop |         10000 | 306,344,550.0 ns | 3,103,025.17 ns | 2,902,571.67 ns |
| WhenDictionaryParallelEnumerable |         10000 |     302,990.9 ns |     3,878.92 ns |     3,028.40 ns |

As expected the foreach loop performs very well in all three cases. What’s interesting is that it outperforms the parallel ForAll() method even when the number of items is very high. We can push this even further but 10k is already a big number of items to hold in a dictionary.

Note that we’ve used a simple assignment for this test. Speed might vary for different logic we want to execute while iterating the dictionary. But in terms of pure speed, the foreach loop wins the race.

The improvement in ForAll() execution speed is visible though. When iterating 100 elements, foreach loop is 126x faster, but with 10k elements, it’s only 8.5x faster than the parallel ForAll() method. Does this justify the usage of ForAll()? Maybe not in this case, but if we plan to execute a more complicated logic during iteration it might be worth it.

Let’s make it a bit more complicated and simply change the assignment to string concatenation (= to +=):

|                           Method | numberOfItems |           Mean |         Error |        StdDev |
|--------------------------------- |-------------- |---------------:|--------------:|--------------:|
|       WhenDictionaryUsingForEach |           100 |       5.102 us |     0.0346 us |     0.0323 us |
|       WhenDictionaryUsingForLoop |           100 |      43.253 us |     0.4601 us |     0.4304 us |
| WhenDictionaryParallelEnumerable |           100 |      58.297 us |     0.3655 us |     0.3419 us |
|       WhenDictionaryUsingForEach |          1000 |     378.531 us |     2.4087 us |     1.8806 us |
|       WhenDictionaryUsingForLoop |          1000 |   3,692.508 us |    28.3033 us |    25.0901 us |
| WhenDictionaryParallelEnumerable |          1000 |     356.056 us |     5.2078 us |     4.6166 us |
|       WhenDictionaryUsingForEach |         10000 |  76,903.129 us |   973.6142 us |   910.7194 us |
|       WhenDictionaryUsingForLoop |         10000 | 392,031.540 us | 4,741.7690 us | 4,435.4537 us |
| WhenDictionaryParallelEnumerable |         10000 |   8,772.161 us |   153.9409 us |   143.9964 us |

This time the results are clearly in the favor of the parallel ForAll() method. Even with the 1000 elements, the execution speed shows improvements, but with the 10k it clearly wins.

Conclusion

So if we need to iterate through a dictionary, we can conclude that using a simple foreach method does the job decently in most cases. We must be careful though because depending on the number of elements and the operation we’re executing, parallel execution might be the way to go.