给定的键在字典中不存在。哪个键?

有没有一种方法可以在 C # 中获得下列异常中给定键的值,从而影响所有泛型类?我认为这是微软在异常描述中的一大遗漏。

"The given key was not present in the dictionary."

更好的办法是:

"The given key '" + key.ToString() + "' was not present in the dictionary."

解决方案可能涉及混合或派生类。

315362 次浏览

In the general case, the answer is No.

However, you can set the debugger to break at the point where the exception is first thrown. At that time, the key which was not present will be accessible as a value in the call stack.

In Visual Studio, this option is located here:

Debug → Exceptions... → Common Language Runtime Exceptions → System.Collections.Generic

There, you can check the Thrown box.


For more specific instances where information is needed at runtime, provided your code uses IDictionary<TKey, TValue> and not tied directly to Dictionary<TKey, TValue>, you can implement your own dictionary class which provides this behavior.

If you want to manage key misses you should use TryGetValue

https://msdn.microsoft.com/en-gb/library/bb347013(v=vs.110).aspx

string value = "";
if (openWith.TryGetValue("tif", out value))
{
Console.WriteLine("For key = \"tif\", value = {0}.", value);
}
else
{
Console.WriteLine("Key = \"tif\" is not found.");
}

This exception is thrown when you try to index to something that isn't there, for example:

Dictionary<String, String> test = new Dictionary<String,String>();
test.Add("Key1","Value1");
string error = test["Key2"];

Often times, something like an object will be the key, which undoubtedly makes it harder to get. However, you can always write the following (or even wrap it up in an extension method):

if (test.ContainsKey(myKey))
return test[myKey];
else
throw new Exception(String.Format("Key {0} was not found", myKey));

Or more efficient (thanks to @ScottChamberlain)

T retValue;
if (test.TryGetValue(myKey, out retValue))
return retValue;
else
throw new Exception(String.Format("Key {0} was not found", myKey));

Microsoft chose not to do this, probably because it would be useless when used on most objects. Its simple enough to do yourself, so just roll your own!

string Value = dic.ContainsKey("Name") ? dic["Name"] : "Required Name"

With this code, we will get string data in 'Value'. If key 'Name' exists in the dictionary 'dic' then fetch this value, else returns "Required Name" string.

You can try this code

Dictionary<string,string> AllFields = new Dictionary<string,string>();
string value = (AllFields.TryGetValue(key, out index) ? AllFields[key] : null);

If the key is not present, it simply returns a null value.

Borrowed from @BradleyDotNET's anwser, here's the extension method version:

public static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
{
if (dictionary.TryGetValue(key, out TValue result))
{
return result;
}
else
{
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
}
}

If anyone still cares in 2021 and can afford to move to .NET Core, .Net 5 finally tells us the name of the offending key:

class Program
{
static void Main(string[] args)
{
Dictionary<string, object> dictionary = new()
{
["foo"] = new object()
};
Console.WriteLine(dictionary["bar"]);
}
}

Will give you:

Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'bar' was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at KnfTest.Program.Main(String[] args)

The same code in .Net Framework 4.8 gives the old dreaded message.

As for those who can't afford moving to Core yet I'm afraid there is no real working solution. Replacing all indexer invocations with TryGetValue is cumbersome -not to say absurd. Subclassing from Dictionary is practically impossible. Yes you can declare a new indexer but then polymorphism goes out the window as your indexer will be called only when using a reference of the subclass. Finally, creating your own Dictionary wrapper might be a solution but that's again a non trivial process, especially if you want to replicate exactly the Dictionary which implements not only IDictionary<TKey,TValue> but also:

System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>
System.Collections.IDictionary
System.Runtime.Serialization.IDeserializationCallback
System.Runtime.Serialization.ISerializable

Quite an overkill "just" for an exception message.

In C# 7.0 or greater, you can Use the out parameter modifer to solve your problem in one line, and only make one call to the indexer. It works in c# 7.0 or greater.

public TItem GetOrException<TKey,TItem>(TKey key) where TKey : IFormattable =>
dictionary.TryGetValue(key, out var item) ? item : throw new KeyNotFoundException( "Key: " + key.ToString() );

You can also opt for the longer try catch block which gives better exception information:

public TItem GetOrException<TKey,TItem>(TKey key) where TKey : IFormattable {
try{ return dictionary[key]; }
catch (KeyNotFoundException e) { throw new KeyNotFoundException("Key:" + key.ToString(), e);}
}