The simplest way to convert a list to a dictionary is to use the Linq ToDictionary() method:
using System.Linq;
var movieList = GetMovieList();
var moviesById = movieList.ToDictionary(keySelector: m => m.Id, elementSelector: m => m);
Code language: C# (cs)
This loops through the list and uses the key/element selector lambdas you passed in to build the dictionary.
In this article, I’ll go into details about how to use ToDictionary() and show how to deal with duplicate keys.
Note: I’m using named parameters – keySelector/elementSelector – for readability purposes.
Table of Contents
With a loop
The non-Linq way to convert a list to a dictionary is to use a loop:
var movies = GetMovieList();
var moviesById = new Dictionary<int, Movie>();
foreach(var movie in movies)
{
moviesById.Add(movie.Id, movie);
}
Code language: C# (cs)
This has slightly better performance than using Linq. I’d recommend using whichever approach you find easier to understand in your specific scenario.
Implicit element selector
These two statements produce the same result:
//explicit element selector
var moviesById = movieList.ToDictionary(keySelector: m => m.Id, elementSelector: m => m);
//implicit element selector
var moviesById = movieList.ToDictionary(keySelector: m => m.Id);
Code language: C# (cs)
If you don’t specify the elementSelector, it will automatically use the items in the list as the elements. This is often what you want if you’re indexing objects by one of their properties.
How to handle duplicate keys
If the list has a duplicate key, ToDictionary() will throw ArgumentException: An item with the same key has already been added.
This is the same exception you get when you try to insert a duplicate key using Dictionary.Add(key, value).
If you don’t expect your data to have duplicate keys, then throwing an exception is the right thing to do.
If it’s possible for your data to have duplicate keys, you’ll have to group by the key and decide what to use for the value. I’ll show three options below using Linq, and one example of how to solve this when you’re looping.
Option 1 – Select an aggregate value for the key
This example is calculating word counts. Words can repeat, so you have to group by the word and select the count as an aggregate value.
var words = new List<string> { "hello", "world", "hello", "code" };
var wordCountMap = words.GroupBy(w => w)
.ToDictionary(keySelector: g => g.Key, elementSelector: g => g.Count());
foreach(var wordCount in wordCountMap)
{
Console.WriteLine($"{wordCount.Key}={wordCount.Value}");
}
Code language: C# (cs)
This outputs the following:
hello=2
world=1
code=1
Code language: plaintext (plaintext)
Option 2 – Select a list of elements for the key
This example is indexing movies by the year they were released. There can be multiple movies per year, and you want the movie objects, so you’ll need a dictionary of lists (Dictionary<int, List<Movie>>). To do this, you can select the group as a list.
var movieList = GetMovieList();
var moviesByYear = movieList.GroupBy(m => m.YearOfRelease)
.ToDictionary(keySelector: g => g.Key, elementSelector: g => g.ToList());
foreach (var movieGroup in moviesByYear)
{
Console.WriteLine($"{movieGroup.Key}={string.Join(", ", movieGroup.Value.Select(s => s.Name))}");
}
Code language: C# (cs)
This outputs:
2014=Godzilla, The Imitation Game
1993=Jurassic Park, Schindler's List
2016=Deadpool, Arrival
2010=Inception
2019=Joker
2018=Black Panther
1975=Jaws
Code language: plaintext (plaintext)
Option 3 – Select first element with the key
When there are duplicate keys, sometimes you’ll want to select one of the items to represent the group. In the simplest scenario, you’d just select the first element in the group. This is the same as selecting distinct objects by a property.
For example, this is indexing movies by year and only selecting one movie for each year:
var movieList = GetMovieList();
var moviesByYear = movieList.GroupBy(m => m.YearOfRelease)
.ToDictionary(keySelector: g => g.Key, elementSelector: g => g.First());
foreach (var movie in moviesByYear)
{
Console.WriteLine($"{movie.Key}={movie.Value.Name}");
}
Code language: C# (cs)
This outputs the following:
2014=Godzilla
1993=Jurassic Park
2016=Deadpool
2010=Inception
2019=Joker
2018=Black Panther
1975=Jaws
Code language: plaintext (plaintext)
Dealing with duplicates when looping
If you’re looping instead of using Linq, you can deal with duplicates by checking if the key exists and choosing how to deal with the duplicates.
In this example, it’s creating a list of movies per year. When it sees the key doesn’t exist yet, it initializes it with an empty list, then adds the movie to the list.
var movies = GetMovieList();
var moviesByYear = new Dictionary<int, List<Movie>>();
foreach (var movie in movies)
{
if(!moviesByYear.ContainsKey(movie.YearOfRelease))
{
moviesByYear.Add(movie.YearOfRelease, new List<Movie>());
}
moviesByYear[movie.YearOfRelease].Add(movie);
}
foreach (var movieGroup in moviesByYear)
{
Console.WriteLine($"{movieGroup.Key}={string.Join(", ", movieGroup.Value.Select(s => s.Name))}");
}
Code language: C# (cs)
This outputs the following:
2014=Godzilla, The Imitation Game
1993=Jurassic Park, Schindler's List
2016=Deadpool, Arrival
2010=Inception
2019=Joker
2018=Black Panther
1975=Jaws
Code language: plaintext (plaintext)
To me, this is a bit easier to understand than the Linq-based approach (using GroupBy() + ToDictionary()).
Comments are closed.