When we build our .NET applications, we commonly use dictionaries to store data as key-value pairs. However, dictionaries do not allow us to map multiple values to a single key. In this article, let’s explore the Lookup class in C# and discuss how we can use it to handle this challenge.
Without further ado, let’s dive in.
What Is a Lookup in C#?
In C#, the Lookup<TKey, TElement>
class is a dictionary-like data structure that maps keys to a value or collection of values. This class implements the ILookup<TKey, TElement>
interface, which we can find in the LINQ namespace.
Instances of the Lookup
class maintain an internal array of Grouping<TKey, TElement>
objects, with each group mapped to a unique key. So, any time we add a value corresponding to a key in the lookup, it adds that value to the key’s group.
The Lookup<TKey, TElement>
class and the Dictionary<TKey, TValue>
class are similar data structures in that we can use them to map a key to a value. However, there are some differences between these types.
In the next section, let’s discuss these differences and see how they affect our applications.
Differences Between a Lookup and a Dictionary in C#
Firstly, a Lookup<TKey, TElement>
instance is an immutable data structure. This means that once we create a Lookup
, we can’t add or remove any item from it. In contrast, a dictionary will allow us to add, delete, or modify its content even after creation.
Secondly, Lookup<TKey, TElement>
objects allow us to map a key to a single value or a collection of values. However, dictionaries only allow us to map each of our keys to a single value.
Finally, lookups do not have a public constructor. We can only create a lookup by invoking the ToLookup()
method on a collection that implements the IEnumerable<T>
interface. On the other hand, when we work with dictionaries, we have access to a variety of public constructor overloads.
Let’s continue talking about something more hands-on and discuss how to create Lookup
objects.
How to Create a Lookup in C#
As we’ve seen before, we can only create lookups by invoking the ToLookup()
method. When we call this method on an enumerable, it performs two actions.
First, it creates a new Lookup
instance that has a Grouping<TKey, TElement>
array field.
After that, it iterates through all the items in our input enumerable. On each iteration, it generates a new Grouping<TKey, TElement>
instance for the current item maps the group to the key we specified and then adds the value we selected to the group.
During this process, if we encounter any duplicate key, then it means that we have already created and mapped a group to it. Therefore, we retrieve that group and add our value to it.
Note that the ToLookup()
method returns instances of the ILookup
interface. This is because it’s always best to return abstract types rather than concrete types when we design APIs.
Now, the input enumerable for our lookup will be a list of students. So, let’s define a Student
record:
public record Student(string Name, string Course);
This record will serve as the type for our input lists and we define the string
properties Name
and Course
.
Great! Let’s now proceed to define our Lookup<TKey, TElement>
object.
Create a Lookup From a List With a Duplicate
For this, let’s first create our list of students:
private static readonly List<Student> _students = [ new("Dan Sorla", "Accounting"), new("Dan Sorla", "Economics"), new("Luna Delgrino", "Finance"), new("Kate Green", "Investment Management") ];
Here, we define a list of students where the first student is entered twice but with different courses.
With that, let’s proceed to create our lookup:
public static ILookup<string, string> CreateLookup() => _students.ToLookup(s => s.Name, s => s.Course);
We pass in two arguments to the ToLookup()
method and invoke it on the _students
list.
The first argument, s => s.Name
is a function for the keySelector
. We use it to specify how our ToLookup()
method should extract a key from each item in our input list. In this case, we specify that we want the names of our students to be the keys in the lookup. Then, the second argument, s => s.Course
is a function for the elementSelector
. We use this selector to specify that we want the courses as values in our lookup.
As we have seen, with these selections, the second appearance of the name, "Dan Sorla"
, will be a duplicate key in our lookup. However, instead of raising an exception when we try to add this name to our lookup – as would be the case with a dictionary – the ToLookup()
method retrieves the initial group mapped to the first entry of the Name
which is our key. Then, it will add the new Course
string to the group.
When all that is done, we get a Lookup
where one key has multiple values mapped to it.
Operations of the Lookup Class
Let’s discuss the various operations we can perform on them. These operations include retrieving items from our lookup, searching our lookup for a key, and retrieving the count of items in our lookup.
As we’ve noted before, lookups are immutable. Therefore, we can’t carry out add, update, and delete operations on our lookup instances.
With that, let’s define an input Lookup
instance for these operations:
private static readonly ILookup<string, string> _lookup = CreateLookup();
Here, we call the CreateLookup()
method we defined in the last section to create the input lookup, having the name of the student as a key, and the courses as the elements.
Let’s divide these operations into different sections and discuss them in depth.
Retrieving Items From a Lookup
Let’s start with retrieving items from our lookups.
First, let’s explore how to get a value from our lookup by specifying its key:
public static IEnumerable<string> RetrieveValuesOfAKeyFromLookup(string key) => _lookup[key];
For this, we use the key of the value we wish to retrieve as an indexer. We get all the values attached to this key as a collection. However, if no value is mapped to this key, we get an empty IEnumerable<string>
instance.
Next, let’s discuss how to retrieve all the keys in our lookup:
public static List<string> RetrieveAllKeysFromLookup() => _lookup.Select(studentGroup => studentGroup.Key).ToList();
We invoke the Select()
method and take the Key
of the Lookup
element.
Moving on, let’s demonstrate how to retrieve all the values in our lookup:
public static IEnumerable<string> RetrieveAllValuesFromLookup() => _lookup.SelectMany(studentGroup => studentGroup);
To retrieve the values in our lookup, we invoke the SelectMany()
method to flatten all the Grouping<TKey, TElement>
objects in our lookup and yield return all the elements (student’s courses) we added to them.
Searching for a Key in a Lookup
Now, as a next step, let’s explore how to search for a key in our lookup:
public static bool SearchForKeyInLookup(string key) => _lookup.Contains(key);
Here, we pass in the key we want to search for to the Contains()
method and invoke it. This method returns true
if the specified key exists in our lookup. Otherwise, it returns false
.
Getting the Count of a Lookup
For the final operation, let’s retrieve the number of items in our lookup:
public static int GetLookupCount() => _lookup.Count;
With the Count
property, we get the total number of key-value pairs in our lookup.
Alright! We’ve successfully examined the various operations we can perform on lookups in C#. Let’s now proceed to discuss some situations where we will find them suitable for use in our applications.
When Should We Use a Lookup in C#?
Generally speaking, we can use lookups any time we want to store data as key-value pairs. However, for simple cases, it is advisable to utilize dictionaries. This is because dictionaries are more performance efficient as we do not define Grouping<TKey, TElement>
objects for their keys.
Also, as we saw in one of our examples, lookups can be useful whenever we want to map multiple values to a particular key.
Additionally, lookups will surely come in handy in cases where we want to easily group our enumerable based on a particular property or set of properties.
Finally, whenever we want to store data in key-value pairs, and we are not sure if all our keys will be unique, it is advisable to utilize a lookup.
Conclusion
In this article, we’ve explored the Lookup class in C#.
First, we explained what a Lookup is. Then, we discussed how to create Lookup instances. After that, we examined different ways of retrieving items from our lookups. Subsequently, we explored how to search for a key in our lookups and how to get the count of items in our lookups.
In the end, we saw four real-world scenarios where lookups are effective tools for handling data in our applications.