In this article, we will explore several ways of how to merge dictionaries in C# and compare their performance to help you choose the best option for your specific use case.
Let’s dive in!
What Is Dictionary in .NET?
Dictionary is a fundamental data structure that enables efficient storage and retrieval of key-value pairs. They provide a flexible and powerful mechanism to store data elements based on unique keys.
There are situations where it becomes necessary to combine or merge multiple dictionaries into a single unified structure. Dictionary merging involves the process of combining key-value pairs from different dictionaries, enabling enhanced data manipulation, aggregation, or consolidation.
Merging Dictionaries Project Setup
We will demonstrate a variety of methods, such as concatenation, iteration, grouping, union, and more, providing an overview of their usage and performance considerations. All these methods can merge dictionaries with the same type of key-value pairs.
Let’s begin by creating a new console app using the dotnet new console -n app
command in the command window, and let’s create two sample dictionaries:
var dictionaryA = new Dictionary<int, string>() { { 1, "Monday" }, { 2, "Tuesday" }, { 3, "Wednesday" } }; var dictionaryB = new Dictionary<int, string>() { { 4, "Thursday" }, { 5, "Friday" }, { 6, "Saturday" }, { 7, "Sunday" } };
Both dictionaries have int
keys and string
values. The keys represent the numeric values of the days of the week, while the values correspond to the names of the days. We initialize the dictionaries using object initialization syntax, which allows concise addition of key-value pairs during creation.
Use ForEach() Iteration for Merging Dictionaries
First, we will simply iterate each dictionary through each key-value pair to create the new dictionary:
public Dictionary<TKey, TValue> ForEachMethod<TKey, TValue>( params Dictionary<TKey, TValue>[] dictionaries) { var mergedDictionary = new Dictionary<TKey, TValue>(); foreach (var dictionary in dictionaries) { foreach (var kvp in dictionary) { if (!mergedDictionary.ContainsKey(kvp.Key)) { mergedDictionary[kvp.Key] = kvp.Value; } } } return mergedDictionary; }
Here, we define the ForEachMethod()
method that merges multiple dictionaries into a single Dictionary
object. This method is generic, with type parameters TKey
and TValue
, allowing the dictionaries to have keys and values of different types. It accepts a variable number of dictionaries
as input using the params keyword, allowing for flexibility in the number of dictionaries to merge.
Inside the method, we create a mergedDictionary
object to store the merged key-value pairs. By using nested foreach
loops, we iterate over each dictionary and its key-value pairs.
For each key-value pair, we check if the key already exists in the mergedDictionary
. If the key is not present, we add the pair to the mergedDictionary
. We perform this check to keep the first value we examine, in case of duplicate keys. Otherwise, if we omit it, we assign the last value of the same key we find.
Finally, we return the mergedDictionary
, containing all unique keys and their corresponding values from the input dictionaries.
We print the returned dictionary and we end up with the following:
Key: 1, Value: Monday Key: 2, Value: Tuesday Key: 3, Value: Wednesday Key: 4, Value: Thursday Key: 5, Value: Friday Key: 6, Value: Saturday Key: 7, Value: Sunday
We can see that the dictionaries are successfully merged into one.
Merge Dictionaries With Aggregate() And Concat()
Next, we can make use of the Concat()
method of LINQ:
public Dictionary<TKey, TValue> ConcatMethod<TKey, TValue>(params Dictionary<TKey, TValue>[] dictionaries) { var mergedDictionary = dictionaries.Aggregate((dict1, dict2) => dict1.Concat(dict2).ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ); return mergedDictionary; }
Here, we define a method called ConcatMethod()
that uses the Aggregate()
extension with a lambda expression.
The Aggregate()
method is a powerful LINQ extension method that helps us perform an iterative computation on a sequence of elements. It takes as parameters an initial value and a lambda function that defines the logic for combining the elements.
The lambda function within the Aggregate()
method takes two arguments, the accumulated value and the current element from the collection. It performs an operation on these values and returns a new result, which becomes the accumulated value for the next iteration. We continue this process until we iterate through all elements in the collection, resulting in a single value.
Here, within our lambda expression, we concatenate each dictionary and convert it into a new dictionary using the Concat()
and ToDictionary()
methods. We should note here that this method has as a prerequisite that the keys in the dictionaries are unique across the given dictionaries.
Finally, we return the mergedDictionary
as the result.
Use SelectMany() And GroupBy() To Merge Dictionaries
Next, let’s use another LINQ extension method:
public Dictionary<TKey, TValue> GroupByMethod<TKey, TValue>( params Dictionary<TKey, TValue>[] dictionaries) { var mergedDictionary = dictionaries.SelectMany(dict => dict) .GroupBy(kvp => kvp.Key) .ToDictionary(group => group.Key, group => group.First().Value); return mergedDictionary; }
Here we create a method called GroupByMethod()
that merges multiple dictionaries into a single one by grouping the key-value pairs based on their keys.
First, we flatten the dictionaries into a sequence of key-value pairs using the SelectMany()
method. Then, we group this sequence by the key, resulting in a collection of groups where each group represents a unique key.
We convert these groups into a dictionary using the ToDictionary()
method, where the key of each group becomes a key in the resulting dictionary, and the value is the value of the first key-value pair within that group.
Use of ToLookup() To Combine Dictionary Collections
Also, we can use LINQ’s ToLookup()
method approach:
public Dictionary<TKey, TValue> LookupMethod<TKey, TValue>(params Dictionary<TKey, TValue>[] dictionaries) { var mergedDictionary = dictionaries.SelectMany(dict => dict) .ToLookup(pair => pair.Key, pair => pair.Value) .ToDictionary(group => group.Key, group => group.First()); return mergedDictionary; }
With the SelectMany()
method we combine all the key-value pairs from each dictionary into a single sequence.
Next, we use the ToLookup()
method to group the key-value pairs by their keys, creating a lookup collection where each key is associated with a collection of corresponding values.
Finally, the lookup collection is converted into a dictionary using the ToDictionary()
method. For each group in the lookup collection, the key of the group becomes a key in the resulting dictionary, and the value is the first value in the collection that associates with that key.
Merge Dictionaries With LINQ’s Union()
Next, we can use the Union()
extension method of LINQ:
public Dictionary<TKey, TValue> UnionMethod<TKey, TValue>(params Dictionary<TKey, TValue>[] dictionaries) { var mergedDictionary = dictionaries.Aggregate((result, dict) => { return result.Union(dict).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); }); return mergedDictionary; }
Here, for each iteration in the Aggregate()
method, we use the Union()
method to combine the key-value pairs from the accumulated result and the current dictionary.
The Union()
method returns a sequence of unique key-value pairs. For this reason, if we have a common key value between our dictionaries, this method will throw an error.
Use Lists to Merge Dictionaries
Our next approach is to use the List
collection:
public Dictionary<TKey, TValue> UsingListsMethod<TKey, TValue>( params Dictionary<TKey, TValue>[] dictionaries) { var keys = new List<TKey>(); var values = new List<TValue>(); foreach (var dictionary in dictionaries) { foreach (var kvp in dictionary) { keys.Add(kvp.Key); values.Add(kvp.Value); } } var mergedDictionary = keys.Zip(values, (key, value) => new KeyValuePair<TKey, TValue>(key, value)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); return mergedDictionary; }
Here, we create two List
objects. One, named keys
, to store the keys, and another named values
, to store the values.
We iterate over each dictionary and its key-value pairs using nested foreach
loops, adding the key to the keys
set, and the value to values
.
Then, we use the Zip()
method to combine the keys and values into a sequence of KeyValuePair
objects. The Zip()
method is useful when we need to apply a function to the elements of two sequences and produce a sequence of the results. Here, we use a lambda expression as the selector function for each pair of corresponding elements, using the List
collection for storing the data, as it guarantees the order of the data.
Finally, we apply the ToDictionary()
method to convert the sequence of key-value pairs into a dictionary. For this reason, if there is a duplicate key between our dictionaries, this method throws an error.
Compare the Methods to Merge Dictionaries
We will evaluate these methods to find the most efficient one in terms of speed, with the benchmark class. Let’s create two helpers methods to set up our benchmark scenario for testing dictionary merging performance:
public void Setup() { _dictionaryA = new Dictionary<int, string>(); _dictionaryB = new Dictionary<int, string>(); // Generate random values for dictionaryA for (var i = 0; i < 100; i++) { var randomValue = GenerateRandomValue(); _dictionaryA.Add(key, randomValue); } // Generate random values for dictionaryB for (int i = 100; i < 200; i++) { var randomValue = GenerateRandomValue(); _dictionaryB.Add(key, randomValue); } } private string GenerateRandomValue() { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; return new string(Enumerable.Repeat(chars, 5).Select(s => s[Random.Shared.Next(s.Length)]).ToArray()); }
In the Setup()
method we initialize two dictionaries, _dictionaryA
and _dictionaryB
.
Initially, we run a loop 100 times for each dictionary to populate them. Then for each iteration, we assign the iterator i
variable as the key and a random value using the GenerateRandomValue()
method, adding the key-value pair to each dictionary.
For _dictionaryA
we iterate from 0 to 99, while for _dictionaryB
we iterate from 100 to 199. In this way, we ensure that the keys are unique between the two data structures.
Using the GenerateRandomValue()
method, we generate a random string
of five characters using alphanumeric ones. We use the Random.Shared static instance to generate random characters. In the method, we use the Enumerable.Repeat()
method to repeat the character string multiple times and then select a random character from each repetition using the random.Next()
method.
Finally, we concatenate the resulting characters into a string and then we return them.
Benchmark Results
Let’s benchmark the proposed methods by merging the generated dictionaries, and check the result:
| Method | Mean | Error | StdDev | Median | Rank | Gen0 | Allocated | |----------------- |----------:|----------:|----------:|----------:|-----:|--------:|----------:| | ForEachBenchmark | 6.459 us | 0.1270 us | 0.1780 us | 6.408 us | 1 | 6.5079 | 9.99 KB | | ConcatBenchmark | 10.908 us | 0.2140 us | 0.3634 us | 10.958 us | 2 | 6.6376 | 10.2 KB | | ListsBenchmark | 17.683 us | 0.4507 us | 0.9000 us | 22.384 us | 3 | 15.1367 | 16.23 KB | | GroupByBenchmark | 31.484 us | 0.5892 us | 0.5511 us | 31.449 us | 4 | 19.1040 | 29.37 KB | | LookupBenchmark | 31.749 us | 0.6296 us | 1.4338 us | 31.357 us | 4 | 18.5547 | 28.51 KB | | UnionBenchmark | 34.687 us | 0.9068 us | 2.6018 us | 33.758 us | 5 | 16.4795 | 25.27 KB |
Based on the outcome, we can see that the ForEachBenchmark()
method stands out as the fastest approach, with the fastest mean execution time. Despite that this method has no complex structure, it outperforms the other methods that use LINQ’s syntax.
The ForEachBenchmark()
method achieves its efficiency by utilizing a simple loop construct to iterate over the dictionaries and perform the merging operation. On the other hand, the ConcatBenchmark()
and ListsBenchmark()
methods demonstrate slightly slower execution times. In contrast, the other approaches show further increases in execution time.
The memory allocation results show that the ListsBenchmark()
, GroupByBenchmark()
, LookupBenchmark()
and UnionBenchmark()
methods allocate much more memory compared to the two fastest methods.
The GroupByBenchmark()
method leverages the LINQ’s GroupBy()
method to group key-value pairs by keys. This grouping operation involves creating additional data structures to organize and group the elements based on the keys, which may contribute to an increase in memory usage.
Lastly, the method that uses the LINQ Union()
method involves creating intermediate collections or enumerators to perform the merging operation, resulting in greater memory usage.
Conclusion
In this article, we explored various different approaches that allow us to merge dictionaries in C#. Then, we evaluated our proposed solutions in terms of performance and efficiency, determining the best choice to suit our requirements.