Dictionaries and sets

C# · Practical · 10 min

Hash-based collections turn slow searches into constant-time lookups and appear in most real code.

Dictionary<TKey, TValue> maps keys to values with average O(1) lookup. Create it with a collection initializer new() { ["a"] = 1 } and use:

  • dict[key] = value adds or overwrites.
  • dict[key] reads, but throws KeyNotFoundException if missing.
  • TryGetValue(key, out var v) reads safely; ContainsKey, Remove, Count.
  • GetValueOrDefault(key, fallback) for a default.
  • foreach (var (key, value) in dict) iterates pairs (order is not guaranteed).

HashSet<T> holds unique items: Add returns false when the item is already present, Contains is O(1), and UnionWith, IntersectWith, ExceptWith do set algebra.

Both rely on the key's GetHashCode and Equals. Strings, numbers and records work out of the box; for your own classes, override both consistently, or keys compare by reference. Never mutate an object while it is a key.

Pass a comparer to change equality, e.g. new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase).

Other useful types: SortedDictionary (ordered by key, O(log n)), Queue<T>, Stack<T>, PriorityQueue<TElement, TPriority>, and the read-optimized FrozenDictionary for lookup tables built once. For thread-safe use from many threads, use ConcurrentDictionary.

Example

var text = "the cat and the hat and the bat";
var counts = new Dictionary<string, int>();
foreach (var w in text.Split(' '))
    counts[w] = counts.GetValueOrDefault(w) + 1;

Console.WriteLine(counts["the"]);              // 3
Console.WriteLine(counts.TryGetValue("dog", out var n) ? n : -1); // -1

var ci = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
    ["Apple"] = 1,
};
Console.WriteLine(ci.ContainsKey("APPLE"));    // True

var seen = new HashSet<string>();
foreach (var w in text.Split(' '))
    if (!seen.Add(w)) Console.Write(w + " ");  // the and the
Console.WriteLine();

var a = new HashSet<int> { 1, 2, 3 };
a.IntersectWith([2, 3, 4]);
Console.WriteLine(string.Join(",", a));        // 2,3

Remember

  • Dictionary lookups are O(1) on average
  • Use TryGetValue or GetValueOrDefault instead of catching KeyNotFoundException
  • HashSet.Add returns false for duplicates
  • Keys need consistent Equals and GetHashCode

Watch out for

  • Using a mutable class without Equals/GetHashCode as a key and expecting equal contents to match.

Check yourself

  1. What happens when you read dict["missing"] on a Dictionary<string,int>?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2

Builds on

Unlocks