C # KeyNotFoundException mit weiteren Informationen
Vorentscheidung:
System.Collections.Generic.KeyNotFoundException : The given key was not present in the dictionary.
Oh. Meine.
Also hatte ich endlich die Nase voll davon, dass Standard-Ausnahmeinformationen nicht mehr nutzlos sind .
Für einen Teil des Projekts verwende ich die this[TKey key]Eigenschaft meistens nicht mehr, um auf meine DictionaryIies zuzugreifen, sondern verwende eine benutzerdefinierte Erweiterungsmethode, die tatsächlich versucht, mir mitzuteilen, dass WTF schief gelaufen ist.
Vorbemerkung:
Das System, das ich habe, verfügt neben umfangreicheren Debug-Unit-Tests über eine große Suite für "Integrationstests". Wenn die Dinge nach Süden gehen, ist es schwierig zu verstehen, was schief gelaufen ist, wenn Sie nur eine eher unspezifische Stapelverfolgung und eine Meldung haben Sie:
System.Collections.Generic.KeyNotFoundException : The given key was not present in the dictionary.
Ich möchte darauf hinweisen, dass dieses System stark auf (Zeichenfolgen-) Zuordnungen angewiesen ist, bei denen ein Konfigurationsfehler solche Fehler leicht an unerwarteten Stellen auslösen kann.
Insbesondere gibt es ziemlich viele Stellen im Code, an denen verschiedene Wörterbücher in demselben Codeteil / derselben Codezeile konsultiert werden , sodass die Stapelverfolgung im Grunde genommen wertlos ist, um das genaue Problem genau zu bestimmen. Überlegen:
var attributeValue = model[Config.PartX].attributes[Config.Attribute42];
Ich werde auch bemerken, dass ich angefangen habe, das TryGetValueZeug von Hand zu codieren , aber das wurde ziemlich schnell unlesbar, deshalb freue ich mich darauf, einen etwas weniger spezifischen, aber hoffentlich brauchbareren Ansatz zu versuchen.
Bedarf
Die Ausnahme "Schlüssel nicht gefunden" muss Informationen enthalten, die Folgendes betreffen:
- lokaler Name des Wörterbuchs
- Anzahl der Elemente im Wörterbuch
- Wert des Schlüssels
Die Informationen werden vom Protokollierungsrahmen (NLog) ohne weiteres Fummeln aufgezeichnet.
Ich habe das Gefühl, dass ich dies ausdrücklich beachten muss: Das potenzielle Auslaufen von
keyWertinformationen ist für dieses System nicht irrelevant. Bitte kommentieren Sie nicht wrt. dieser Aspekt.
Code
Falls zutreffend, fallen alle meine Schnipsel grundsätzlich unter https://opensource.org/licenses/unlicense im unwahrscheinlichen Fall möchte jemand dies kopieren.
namespace MY.Project
{
public static class DictionaryHelper
{
/// <summary>
/// Same as `TValue this[TKey key] { get; }`, but with a better exception message
/// containing the dictionary "name" (needs to be provided), number of entries and key value
/// </summary>
/// <returns>Value if found, throws KeyNotFoundException otherwise</returns>
public static TValue GetExistingValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, string nameOfDict, TKey key)
{
if (!dict.TryGetValue(key, out var val))
{
throw CreateKeyNotFoundException(dict, nameOfDict, dict.Count, key);
}
return val;
}
/// <see cref="GetExistingValue"/> for `IDictionary` above.
public static TValue GetExistingValue<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, string nameOfDict, TKey key)
{
if (!dict.TryGetValue(key, out var val))
{
throw CreateKeyNotFoundException(dict, nameOfDict, dict.Count, key);
}
return val;
}
/// <summary>
/// Provide separate explicit overload for the `Dictionary` class because this class implements both
/// the IDict and IReadOnlyDict interface, making the overload ambiguous otherwise
/// </summary>
public static TValue GetExistingValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, string nameOfDict, TKey key)
{
return GetExistingValue((IDictionary<TKey, TValue>)dict, nameOfDict, key);
}
private static KeyNotFoundException CreateKeyNotFoundException<T, TCount, TKey>(T dict, string nameOfDict, TCount count, TKey key)
{
return new KeyNotFoundException(
$"{nameOfDict} ({dict.GetType()}) (with #{count} entries) does not contain key <{key}>!"
);
}
}
Fragen
Über das allgemeine Feedback hinaus:
- Gibt es einen vernünftigen Namen für diese Erweiterungsmethode?
- Würden Sie dies zu einer Erweiterungsmethode machen?
- Gibt es eine andere gute Möglichkeit, den Namen der Wörterbuchvariablen zu erfassen?
- Reihenfolge der Parameter? Wie kann verhindert werden, dass die Reihenfolge durcheinander gebracht wird
string nameOfDictundkeywennTKey == string?
Antworten
Eine andere Art, diese Katze zu häuten, besteht darin, das Wörterbuch einzuwickeln. Etwas in dieser Richtung
public class DictionarWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> innerDictionary;
private readonly string name;
public DictionarWrapper(IDictionary<TKey, TValue> innerDictionary, string name)
{
// could throw if null or set to empty
this.name = name ?? string.Empty;
this.innerDictionary = innerDictionary;
}
public DictionarWrapper(string name) : this(new Dictionary<TKey, TValue>(), name)
{
}
Die meisten Methoden würden Sie nur wieder in das innerDictionary verketten, aber mit der Indexerfunktion würden Sie Ihren Code einfügen
public TValue this[TKey key]
{
get {
// either trap error or write own TryGetValue and skip calling the normal inner indexer
try
{
return innerDictionary[key];
}
catch (KeyNotFoundException ex)
{
throw new KeyNotFoundException($"{name} ({innerDictionary.GetType()}) (with #{Count} entries) does not contain key <{key}>!", ex);
}
}
set => innerDictionary[key] = value;
}
Sie müssen Ihren Code, der auf das Wörterbuch zugreift, nicht ändern und erhalten trotzdem die gewünschten Ausnahmeinformationen. Wenn Sie den Namen im Konstruktor übergeben, müssen Sie sich nicht darum kümmern, ihn jedes Mal weiter zu übergeben.
Sie können auch den Konstruktor (en) hinzufügen, um ihn wie die normalen Dictionary-Konstruktoren zu gestalten. Dann müssen Sie nur noch ändern, wo der Code das Wörterbuch erstellt, um Ihr Wrapperr zu sein
Ich bin auch der Meinung, dass das Umschließen einer Klasse Standard in der Programmierung ist, siehe Decorator Pattern, sowie Informationen zur zukünftigen Wartung und für jemanden, der nach Ihnen kommt, der keine neuen Erweiterungsmethoden kennen muss.