System.Collections.Generic.KeyNotFoundException

A KeyNotFoundException is thrown when you read a key from a dictionary, and the key is not there. The message names the key, such as The given key 'SKU-9' was not present in the dictionary. It is also thrown by other keyed collections and by some configuration APIs.

The fix is almost always to look the key up in a way that does not assume it exists.

Minimum version: >= 2.0 >= Core 1.0

Statistics

31
elmah.io logo 21

Common causes

The key you asked for is not the same as any key in the dictionary. These are the usual reasons.

A key that was never added

The indexer of a Dictionary assumes that the key exists. Data that is read from a file, a database or a request may not have the key you expected.

var prices = new Dictionary<string, decimal> { ["SKU-1"] = 10m };
var price = prices["SKU-9"];   // KeyNotFoundException

Use TryGetValue, and decide what to do when the key is missing:

var prices = new Dictionary<string, decimal> { ["SKU-1"] = 10m };

if (prices.TryGetValue("SKU-9", out var price))
{
    Console.WriteLine(price);
}
else
{
    Console.WriteLine("Unknown product.");
}

String keys are case sensitive by default. A header or a setting that is called Content-Type in one place and content-type in another is two different keys.

var headers = new Dictionary<string, string> { ["Content-Type"] = "application/json" };
var type = headers["content-type"];   // KeyNotFoundException

Give the dictionary a comparer that ignores the case:

var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    ["Content-Type"] = "application/json"
};
var type = headers["content-type"];

A class without Equals and GetHashCode is compared by reference. Two objects with the same values are two different keys, and the lookup with a new object finds nothing.

public class ProductKey
{
    public string Sku { get; set; }
}

Use a record, which compares by value:

public record ProductKey(string Sku);

Dictionary is not safe to use from several threads at once. A write at the same time as a read can make a key that exists look missing, or corrupt the dictionary.

Use ConcurrentDictionary, or protect the dictionary with a lock.

How to fix it and prevent it

Use TryGetValue or GetValueOrDefault

Both give you a way to handle the missing key without an exception. GetValueOrDefault is the shortest when a default value is fine.

var prices = new Dictionary<string, decimal> { ["SKU-1"] = 10m };
var price = prices.GetValueOrDefault("SKU-9", 0m);

For counters and caches, use TryAdd or ConcurrentDictionary.GetOrAdd, so a key is created the first time it is used.

var counts = new Dictionary<string, int>();
counts["SKU-1"] = counts.GetValueOrDefault("SKU-1") + 1;

If keys are missing because of a data problem, fix the source. Log the key and the keys that exist, so you can see if it is a casing, whitespace or version problem.

How to read the stack trace

The message has the key, and the frame below the dictionary is the line that used the indexer:

System.Collections.Generic.KeyNotFoundException: The given key 'SKU-9' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Shop.Services.PriceBook.Get(String sku) in C:\src\Shop\Services\PriceBook.cs:line 12
   at Program.<Main>$(String[] args) in C:\src\Shop\Program.cs:line 4

The key was SKU-9, and the lookup is on line 12 of PriceBook.cs. Search the code that fills the dictionary, and check whether it ever adds that key, or adds it with another casing.

Should you catch it?

Prefer a check with TryGetValue. It is faster than an exception, and it makes the missing key a normal path.

A catch can make sense at the edge of the application, for example around a handler for messages from outside, where a missing key means that the message is invalid.

Find KeyNotFoundException before your users do

elmah.io logs every unhandled exception in your .NET application with its stack trace and request details, groups identical errors and notifies you when a new one appears.

Start free trial
Free 21-day trial No credit card required

Related exceptions

Frequently asked questions

How do I avoid KeyNotFoundException?

Use TryGetValue or GetValueOrDefault for any key that you do not know exists. Use the indexer only when a missing key is a bug.

Check the casing, spaces and the type of the key. A string with a trailing space, or an object that compares by reference, is not the key you think it is.

Yes, for a single thread, but it looks the key up twice. TryGetValue does it in one step and is safe against changes between the two calls.

The indexer of IConfiguration returns null for a missing key. A KeyNotFoundException from JSON code often comes from GetProperty. Use TryGetProperty instead.

Let your AI agent track it down

Connect Claude Code, Cursor, VS Code or Visual Studio to the elmah.io MCP server and ask your agent to look into KeyNotFoundException for you. For example:

AI chat
Find the most frequent KeyNotFoundException in my production log and show me the line that throws it.

The agent reads the stack trace and request details from elmah.io, finds the code in your repository and proposes a fix. In Claude Code, add the server with one command:

claude mcp add --transport http --client-id claudecode elmahio https://mcp.elmah.io/mcp

The MCP server is included on every plan and is currently in beta. Set up the MCP server.

Further reading

YouTube videos

Answers from Stack Overflow

Year 2017

I have solved your same error simply adding the charset to the connection string:

Server=myServer;Port=3306;Database=myDB15;User ID=usr33;Password=usr33P;CharSet=utf8;

In my case I'm using MySql Connector for .Net version 6.9.3. to connect to 30 equal databases with the same structure, same collation (utf8_unicode_ci) and different table contents.

When I use the MySqlCommand.ExecuteReader() method to select content from user table, in some databases (4 of 30) a got the same error The given key was not present in the dictionary.

Year 2022

Some years later I had the same problem with ObjectContext.ExecuteStoreQuery(), but right now using MySql.Data and MySql.Data.EntityFramework both on version 8.0.27 and when migrating a database from MySQL 5.7.38 to 8.0.28 or 8.0.30.

Adding CharSet or Allow User Variables to the connection string didn't work (and it wasn't possible to change my SELECT or Server settings).

Only updating MySql.Data and MySql.Data.EntityFramework to version 8.0.30 solved the problem.

By Rafael Neto. Read the original answer on Stack Overflow.

The System.Collections.Generic.Dictionary`2 means that the type is System.Collections.Generic.Dictionary, with two type arguments. So in this case it means that the type is System.Collections.Generic.Dictionary<TKey, TValue>, as we all know it.

By Patrick Hofman. Read the original answer on Stack Overflow.

You somehow convinced the C# compiler that your source code was written in code page 1251, the default system code page in Eastern Europe and Russia. That's usually caused by the text file missing the utf-8 BOM. Unclear how this happened, maybe you created the file with a text editor other than the one built into Visual Studio. Maybe it got mangled by source control, the ones with a Unix background tend to drop the BOM.

Open the source file in Visual Studio and ensure it still reads correctly. Then use File > Save As, click the arrow on the Save button, select "with encoding" and pick "Unicode (UTF-8 with signature)".

Also make sure that the default is still good. File > Advanced Save Options > change the Encoding if necessary. If you habitually use another text editor then you'll want configure it so it saves files with a BOM.

By Hans Passant. Read the original answer on Stack Overflow.

This's the way .Net makes classes' names. The initial declaration

 Dictionary<K, V>

will be turned into Dictionary'2 type name where '2 means two generic parameters:

 // Dictionary`2 - two generic parameters
 Console.WriteLine(typeof(Dictionary<int, string>).Name);

 // List`1 - one generic parameter
 Console.WriteLine(typeof(List<int>).Name);

Please compare:

 // IDictionary`2 - two generic parameters
 Console.WriteLine(typeof(IDictionary<int, string>).Name);

 // IDictionary - no generic parameters
 Console.WriteLine(typeof(System.Collections.IDictionary).Name);

By Dmitrii Bychenko. Read the original answer on Stack Overflow.

Let me try to answer all of your questions:


  1. Is this expected behavior and is it documented somewhere?

Yes, it is documented in the C# 6.0 Language Specification under sections §7.6.11.2 Object initializers and §7.6.11.3 Collection initializers.

The syntax

var a =
    new Test
    {
        [1] = "foo"
        [2] = "bar"
    };

was actually newly introduced in C# 6.0 as an extension of the previous object initialization syntax to indexers. An object initializer used together with new (see object creation expression, §7.6.11) always translates to object instantiation and member access of the corresponding object (using a temporary variable), in this case:

var _a = new Test();
_a[1] = "foo";
_a[2] = "bar";
var a = _a;

The collection initializer goes similar besides that each element of the initializer is passed as an argument to the Add method of the newly created collection:

var list = new List<int> {1, 2};

becomes

var _list = new List<int>();
_list.Add(1);
_list.Add(2);
var list = _list;

An object initializer can also contain other object or collection initializers. The specification states for the case of collection initializers:

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property or indexer, the elements given in the initializer are added to the collection referenced by the target.

So a sole collection initalizer used within an object initializer will not attempt to create a new collection instance. It will only try to add the elements to an exisiting collection, i.e. a collection that was already instantiated in the constructor of the parent object.

Writing

[1] = new List<string> { "str1", "str2", "str3" }

is actually a totally different case because this is an object creation expression which only contains an collection initializer, but isn't one.


  1. Why is the syntax above allowed but the following syntax is not?

    List<string> list = { "test" };
    

Now, this is not a collection initializer anymore. A collection initalizer can only occur inside an object initializer or in an object creation expression. A sole { obj1, obj2 } next to an assignment is actually an array initializer (§12.6). The code does not compile since you can't assign an array to a List<string>.


  1. Why is the following syntax not allowed then?

    var dict = new Dictionary<int, string[]> 
    {
        [1] = { "test1", "test2", "test3" },
        [2] = { "test4", "test5", "test6" }     
    };
    

It is not allowed because collection initalizers are only allowed to initialize collections, not array types (since only collections have an Add method).

By adjan. Read the original answer on Stack Overflow.