← Debugging Guides

阅读中文版本

C# KeyNotFoundException: common causes

Fix C# KeyNotFoundException from Dictionary lookups by using TryGetValue, GetValueOrDefault, or validating the key before access.

beginnerdebuggingcsharpcollections

Symptoms

  • System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary
  • A lookup works for known keys but crashes on missing ones
  • The dictionary key came from user input or a config file

Possible causes

  • The key is absent and indexer [] is used instead of TryGetValue
  • Case sensitivity differs between insert and lookup
  • The dictionary was populated from a source missing some keys
  • Concurrent modification removed the key between check and access

Diagnosis steps

Log the missing key value

catch (KeyNotFoundException e) { logger.LogError("key {Key}", key); }

Interpretation

Seeing the exact key reveals whether it is a case, whitespace, or genuinely absent value.

Inspect dictionary contents around the access

Interpretation

Printing keys shows the expected key is not present, often due to a formatting mismatch.

Switch to a non-throwing lookup

dict.TryGetValue(key, out var value)

Interpretation

If TryGetValue returns false, the key is truly absent and the caller must handle it.

Root cause

Dictionary indexer access throws KeyNotFoundException when the key does not exist, unlike some other languages that return a default. The key may be absent because of case sensitivity, whitespace, or because upstream data did not populate it, and the compiler offers no protection for the lookup.

Fix

Use TryGetValue or CollectionExtensions.GetValueOrDefault for optional keys, and validate required keys with a clear domain exception. Match the comparer to the data (StringComparer.OrdinalIgnoreCase when case-insensitive), and normalise keys before insert and lookup so the two agree.

Verify the fix

Run the lookup with the previously missing key; with TryGetValue the code returns the fallback cleanly, and a test asserts the missing-key path no longer throws.

Symptoms

A dictionary lookup throws KeyNotFoundException. It happens only for keys not present in the dictionary, so it survives until real-world data arrives.

Diagnosis

Capture the key and inspect the contents:

try {
    var v = dict[key];
} catch (KeyNotFoundException) {
    logger.LogError("missing key: [{Key}]", key);
    throw;
}

Case or whitespace differences are common: "User vs "user".

Fix

Prefer non-throwing lookups:

if (dict.TryGetValue(key, out var value)) {
    return value;
}
return defaultValue;

// or
var v = dict.GetValueOrDefault(key, fallback);

Use a case-insensitive comparer when appropriate:

var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

Verification

Call the lookup with the key that used to be missing; no exception is thrown and the fallback is returned. A test asserts the missing-key path.

Choosing Lookups by Contract

The right accessor depends on whether the key is required. A missing key that indicates a programming error should fail fast, so an explicit check with a domain exception is clearer than a bare indexer. A missing key that is normal — an optional config override, a cache miss — should never throw, so TryGetValue or GetValueOrDefault is correct. Mixing the two is what makes KeyNotFoundException reappear: a lookup that once assumed presence keeps throwing when data changes. Normalising keys with a single comparer, and documenting which keys are mandatory, keeps insert and lookup in agreement as the schema evolves.