.NET Tools How-To's

Reduce Collection Lookups With ReSharper

The 2023.1 ReSharper and JetBrains Rider releases introduce a set of inspections and quick-fixes that help you optimize and speed up your code when working with different collections.

Many C# projects contain numerous places where developers add items to collections like HashSet and Dictionary. Quite often, there’s a check to verify whether the item is already in the collection. However, this may be quite wasteful, as the Add() and TryAdd() methods on these collections already do that check for you – occupying the CPU with the same instructions twice!

Let’s look at some examples!

Working with HashSet<T>

When writing code using HashSet<T>, there’s a good chance your code uses the following pattern. For example, this snippet checks whether the person is part of the people hashset and only adds the person if it’s absent.

var person = new Person("Maarten", "Balliauw");
if (!people.Contains(person))
{
    people.Add(person);
}

You don’t have to check if an item is already in a HashSet<T> using the Contains() method! If you look at the source code for HashSet<T>, you’ll see both Contains() and Add() check whether the hashset contains an item. In other words, you’re doing the same operation twice.

If you are doing this “contains check” occasionally, and on small collections, the performance impact of running the check will be minimal. However, you can reduce CPU time by using only Add() when doing this often with more significant collections. These situations are where ReSharper and JetBrains Rider can help you!

A new inspection informs you about this redundant check, and provides a Remove redundant .Contains quick-fix that can automatically remove this check. And as with many quick-fixes, you can update the usage of this pattern in our entire project or solution in one go.

Note that ReSharper and JetBrains Rider make these inspections and quick-fixes available on HashSet<T> and other types that implement ISet.

Working with Dictionary<TKey, TValue>

When working with a Dictionary instance, a similar inspection and quick-fix is available when using a combination of ContainsKey() and Add().

if (employees.ContainsKey(id))
{
    employees[id] = employee;
}
else
{
    employees.Add(id, employee);
}

When using a dictionary’s indexer to set a value, the TryInsert() method is used internally to add or update the entry in the dictionary. The Remove redundant .ContainsKey quick-fix can update your code in one go:

There are some additional inspections when working with dictionaries. Another common pattern is adding an entry only when its key is not yet present in the dictionary:

if (!employees.ContainsKey(id))
{
    employees.Add(id, employee);
}

Also, ReSharper and JetBrains Rider come with a suggestion: Dictionary and  Dictionary<TKey, TValue> have a TryAdd() method, which does the same. Using the Simplify with TryAdd quick-fix, you can reduce the number of lookups from 2 to just 1. 

A similar inspection and quick-fix is available to encourage the use of TryGetValue(key, out value) for IDictionary collections. The Simplify with TryGetValue quick-fix again removes one lookup in such a situation. Here’s a before and after:

// Before:
if (employees.ContainsKey(id))
{
    return employees[id];
}

// After:
if (employees.TryGetValue(id, out var employee))
{
    return employee;
}

Conclusion

ReSharper and JetBrains Rider come with a set of inspections and corresponding quick-fixes to optimize your work with different types of collections by reducing the number of lookups in collections. Especially when working with larger collections or frequently adding entries, optimizing the usage pattern will improve the performance of your code.

Try the latest ReSharper or download JetBrains Rider! Let us know what you think of these new inspections.

image description