Back to Blog
C#

Using C# Dictionary TryGetValue for Safe Lookups

c# dictionary trygetvalue: Understand how Dictionary.TryGetValue performs a single lookup for key existence and value retrieval, and when it beats ContainsKey plus the...

DictionaryTryGetValueC# CollectionsConcurrencyLookup Performance
Editorial illustration of a dictionary key lookup using TryGetValue, showing a single arrow connecting a key to its stored value

c# dictionary trygetvalue requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The Double Lookup Problem

Dictionary<TKey, TValue>.TryGetValue is the standard method for retrieving a value from a dictionary while confirming the key exists, all in a single lookup. In C#, TryGetValue is frequently preferred over a ContainsKey check followed by the indexer, because it avoids a second hash computation.

Consider the pattern that TryGetValue replaces:

if (dictionary.ContainsKey(key)) { var value = dictionary[key]; // use value }

That code performs two separate lookups. ContainsKey computes the hash and traverses the bucket chain, and then the indexer repeats the same work to fetch the value. For a dictionary with a large number of entries, or a key type with an expensive hash function, the redundant work is measurable.

TryGetValue Syntax and Return Behavior

TryGetValue takes the key and an out parameter for the value. It returns true when the key is present and copies the stored value into the out parameter. When the key is absent, it returns false and writes the default value for TValue into the parameter.

if (dictionary.TryGetValue(key, out var value)) { Console.WriteLine($"Found: {value}"); } else { Console.WriteLine("Key not present"); }

The out var inline declaration, available since C# 7, removes the need to declare a separate variable before the call. The variable is scoped to the enclosing block, which keeps the code compact and avoids an unused-variable warning when the key is missing.

Why TryGetValue Avoids a Second Lookup

The indexer dictionary[key] computes the key's hash and walks the bucket chain to find the entry. Calling ContainsKey and then the indexer repeats that hash computation and bucket traversal. TryGetValue performs the hash computation once and writes the result directly into the out parameter.

The savings matter most when the key type has a non-trivial GetHashCode implementation, or when the dictionary is large enough that bucket traversal touches multiple entries. The exact cost depends on the key type and the dictionary's load factor, so the benefit is structural rather than a fixed number.

Handling Null Values Correctly

A dictionary can store null as a value when TValue is a reference type. TryGetValue returns true when the key exists even if the stored value is null. The return value, not the retrieved value, is the authority on key presence.

var dictionary = new Dictionary<string, string?> { ["config"] = null }; if (dictionary.TryGetValue("config", out var value)) { // The key exists. value is null. // A subsequent check like value != null would be misleading here. }

Code that tests if (value != null) after a successful TryGetValue incorrectly treats an existing key with a null value as absent. If null is a meaningful stored value in your data, branch on the boolean result first.

Value Types and Default Values

For value-type values, a failed TryGetValue writes the default value into the out parameter: 0 for numeric types, false for bool, and the zero-initialized struct for custom value types.

var counts = new Dictionary<string, int>(); if (counts.TryGetValue("orders", out int count)) { // count holds the stored value } else { // count is 0 }

Because the default value is written on failure, you cannot distinguish "key absent" from "key present with the default value" using the out parameter alone. The boolean return value is required for that distinction.

ContainsKey vs TryGetValue: When Each Makes Sense

ContainsKey is appropriate when you only need to know whether a key exists and do not need the value. TryGetValue is the better choice when you need both the existence check and the value, because it performs a single lookup.

OperationLookups performedBest use
ContainsKey then indexerTwoWhen the value is rarely needed after the check
TryGetValueOneWhen the value is needed on success

There is no performance advantage to ContainsKey when the value is required, because the indexer lookup duplicates the hash computation. When only existence matters, ContainsKey avoids writing to an out parameter and reads slightly less state.

Incremental Population Pattern

A frequent use of TryGetValue is incrementally building counts or aggregations:

if (counts.TryGetValue(word, out int current)) { counts[word] = current + 1; } else { counts[word] = 1; }

This is clearer than a ContainsKey check followed by an indexer read and write, and it avoids the KeyNotFoundException that occurs when code reads the indexer without first confirming the key exists. The pattern also works for building lists or other accumulated structures where the first occurrence of a key needs different initialization.

Compatibility Notes

TryGetValue has been part of Dictionary<TKey, TValue> since .NET Framework 2.0, so it is available in every modern .NET implementation. The out var inline declaration requires C# 7 or later. On older language versions, declare the variable before the call:

int count; if (counts.TryGetValue("orders", out count)) { // use count }

The method signature itself — bool TryGetValue(TKey key, out TValue value) — has not changed across versions, so code written against older frameworks compiles unchanged on newer runtimes.

Thread Safety and Concurrent Dictionaries

Dictionary<TKey, TValue> is not thread-safe for concurrent reads and writes. TryGetValue does not change that: if another thread modifies the dictionary while TryGetValue runs, the operation can throw or return inconsistent results. For concurrent scenarios, use ConcurrentDictionary<TKey, TValue>, which provides TryGetValue with the same signature and adds thread-safe behavior.

ConcurrentDictionary also offers GetOrAdd and AddOrUpdate, which combine lookup and modification into atomic operations. For concurrent aggregation, those methods are usually a better fit than a TryGetValue followed by a separate write, because the combined operation avoids the race between reading and writing.

c# dictionary trygetvalue: Practical Usage and Code Examples | RYUSLOG DEV