Der Indexer in Dictionary löst eine Ausnahme aus, wenn der Schlüssel fehlt. Gibt es eine Implementierung von IDictionary, die stattdessen default (T) zurückgibt?
Ich weiß über die "TryGetValue" -Methode Bescheid, aber das ist mit linq nicht möglich.
Würde dies effizient tun, was ich brauche ?:
myDict.FirstOrDefault(a => a.Key == someKeyKalue);
Ich glaube nicht, dass dies der Fall ist, da ich denke, dass es die Schlüssel wiederholt, anstatt eine Hash-Suche zu verwenden.
In der Tat wird das überhaupt nicht effizient sein.
Sie können immer eine Erweiterungsmethode schreiben:
public static TValue GetValueOrDefault<TKey,TValue>
(this IDictionary<TKey, TValue> dictionary, TKey key)
{
TValue ret;
// Ignore return value
dictionary.TryGetValue(key, out ret);
return ret;
}
Oder mit C # 7.1:
public static TValue GetValueOrDefault<TKey,TValue>
(this IDictionary<TKey, TValue> dictionary, TKey key) =>
dictionary.TryGetValue(key, out var ret) ? ret : default;
Das verwendet:
Das Tragen dieser Erweiterungsmethoden kann helfen.
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key)
{
return dict.GetValueOrDefault(key, default(V));
}
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, V defVal)
{
return dict.GetValueOrDefault(key, () => defVal);
}
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, Func<V> defValSelector)
{
V value;
return dict.TryGetValue(key, out value) ? value : defValSelector();
}
Collections.Specialized.StringDictionary
liefert ein Ergebnis ohne Ausnahme, wenn der Wert eines fehlenden Schlüssels nachgeschlagen wird. Die Groß-/Kleinschreibung wird standardmäßig nicht beachtet.
Es ist nur für seine speziellen Verwendungszwecke gültig und - da es vor Generika entwickelt wurde - hat es keinen sehr guten Enumerator, wenn Sie die gesamte Kollektion durchsehen müssen.
Wenn jemand mit .net Core 2 oder höher (C # 7.X) verwendet wird, wird die Klasse CollectionExtensions eingeführt und kann die Methode GetValueOrDefault verwenden, um den Standardwert abzurufen, wenn der Schlüssel nicht im Wörterbuch vorhanden ist.
Man könnte eine Schnittstelle für die Schlüsselsuchfunktion eines Wörterbuchs definieren. Ich würde es wahrscheinlich so definieren:
Interface IKeyLookup(Of Out TValue)
Function Contains(Key As Object)
Function GetValueIfExists(Key As Object) As TValue
Function GetValueIfExists(Key As Object, ByRef Succeeded As Boolean) As TValue
End Interface
Interface IKeyLookup(Of In TKey, Out TValue)
Inherits IKeyLookup(Of Out TValue)
Function Contains(Key As TKey)
Function GetValue(Key As TKey) As TValue
Function GetValueIfExists(Key As TKey) As TValue
Function GetValueIfExists(Key As TKey, ByRef Succeeded As Boolean) As TValue
End Interface
Die Version mit nicht generischen Schlüsseln würde Code ermöglichen, der Code verwendet, bei dem nicht strukturelle Schlüsseltypen verwendet wurden, um willkürliche Schlüsselabweichungen zuzulassen, die mit einem generischen Typparameter nicht möglich wären. Es sollte nicht erlaubt sein, eine veränderliche Dictionary(Of Cat, String)
als veränderliche Dictionary(Of Animal, String)
zu verwenden, da letztere SomeDictionaryOfCat.Add(FionaTheFish, "Fiona")
zulassen würde. Die Verwendung einer veränderlichen Dictionary(Of Cat, String)
als unveränderliche Dictionary(Of Animal, String)
ist jedoch nicht verkehrt, da SomeDictionaryOfCat.Contains(FionaTheFish)
als perfekt formulierter Ausdruck betrachtet werden sollte (er sollte false
zurückgeben, ohne das Wörterbuch nach etwas durchsuchen zu müssen, das nicht vom Typ Cat
ist).
Leider ist die einzige Möglichkeit, eine solche Schnittstelle tatsächlich zu verwenden, die, wenn ein Dictionary
-Objekt in eine Klasse eingeschlossen wird, die die Schnittstelle implementiert. Je nachdem, was Sie tun, lohnt es sich jedoch möglicherweise, eine solche Schnittstelle und die Abweichung, die sie zulässt, zu lohnen.
Diese Frage half zu bestätigen, dass TryGetValue
hier die FirstOrDefault
-Rolle spielt.
Eine interessante C # 7-Funktion, die ich gerne erwähnen möchte, ist die out-Variable - Funktion. Wenn Sie den null-bedingten Operator aus C # 6 zur Gleichung hinzufügen, könnte Ihr Code viel einfacher sein, ohne dass dies erforderlich wäre zusätzliche Erweiterungsmethoden.
var dic = new Dictionary<string, MyClass>();
dic.TryGetValue("Test", out var item);
item?.DoSomething();
Der Nachteil ist, dass Sie nicht alles so inline machen können.
dic.TryGetValue("Test", out var item)?.DoSomething();
Wenn wir dies brauchen/wollen, müssen wir eine Erweiterungsmethode wie Jon's codieren.
public class DefaultIndexerDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private IDictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();
public TValue this[TKey key]
{
get
{
TValue val;
if (!TryGetValue(key, out val))
return default(TValue);
return val;
}
set { _dict[key] = value; }
}
public ICollection<TKey> Keys => _dict.Keys;
public ICollection<TValue> Values => _dict.Values;
public int Count => _dict.Count;
public bool IsReadOnly => _dict.IsReadOnly;
public void Add(TKey key, TValue value)
{
_dict.Add(key, value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_dict.Add(item);
}
public void Clear()
{
_dict.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _dict.Contains(item);
}
public bool ContainsKey(TKey key)
{
return _dict.ContainsKey(key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dict.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dict.GetEnumerator();
}
public bool Remove(TKey key)
{
return _dict.Remove(key);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return _dict.Remove(item);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dict.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return _dict.GetEnumerator();
}
}
Hier ist eine Version von @ JonSkeet für die Welt von C # 7.1, die auch die Übergabe einer optionalen Voreinstellung ermöglicht:
public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue;
Es kann effizienter sein, zwei Funktionen zu haben, um den Fall zu optimieren, in dem Sie default(TV)
zurückgeben möchten:
public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue) => dict.TryGetValue(key, out TV value) ? value : defaultValue;
public static TV GetValueOrDefault2<TK, TV>(this IDictionary<TK, TV> dict, TK key) {
dict.TryGetValue(key, out TV value);
return value;
}
Leider gibt es in C # (noch nicht?) Einen Kommaoperator (oder den von C # 6 vorgeschlagenen Semikolonoperator), sodass Sie für eine der Überladungen einen tatsächlichen Funktionskörper (Keuchen!) Haben müssen.
Wenn Sie ASP.NET MVC verwenden, können Sie die RouteValueDictionary-Klasse verwenden, die den Job ausführt.
public object this[string key]
{
get
{
object obj;
this.TryGetValue(key, out obj);
return obj;
}
set
{
this._dictionary[key] = value;
}
}
Es kann ein Einzeiler sein, der TryGetValue
überprüft und den Standardwert zurückgibt, wenn es false
ist.
Dictionary<string, int> myDic = new Dictionary<string, int>() { { "One", 1 }, { "Four", 4} };
string myKey = "One"
int value = myDic.TryGetValue(myKey, out value) ? value : 100;
myKey = "One"
=> value = 1
myKey = "two"
=> value = 100
myKey = "Four"
=> value = 4
Ich habe Encapsulation verwendet, um ein IDictionary mit einem Verhalten zu erstellen, das einer STL-Map sehr ähnlich ist - für diejenigen von Ihnen, die mit C++ vertraut sind. Für diejenigen, die nicht sind:
TL/DR - SafeDictionary wurde so geschrieben, dass es unter keinen Umständen zu Ausnahme von perversen SzenarienAusnahmen auslöst, z. Dazu wird Add durch das Verhalten von AddOrUpdate ersetzt und default zurückgegeben, anstatt NotFoundException aus dem Indexer auszulösen.
Hier ist der Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class SafeDictionary<TK, TD>: IDictionary<TK, TD> {
Dictionary<TK, TD> _underlying = new Dictionary<TK, TD>();
public ICollection<TK> Keys => _underlying.Keys;
public ICollection<TD> Values => _underlying.Values;
public int Count => _underlying.Count;
public bool IsReadOnly => false;
public TD this[TK index] {
get {
TD data;
if (_underlying.TryGetValue(index, out data)) {
return data;
}
_underlying[index] = default(TD);
return default(TD);
}
set {
_underlying[index] = value;
}
}
public void CopyTo(KeyValuePair<TK, TD>[] array, int arrayIndex) {
Array.Copy(_underlying.ToArray(), 0, array, arrayIndex,
Math.Min(array.Length - arrayIndex, _underlying.Count));
}
public void Add(TK key, TD value) {
_underlying[key] = value;
}
public void Add(KeyValuePair<TK, TD> item) {
_underlying[item.Key] = item.Value;
}
public void Clear() {
_underlying.Clear();
}
public bool Contains(KeyValuePair<TK, TD> item) {
return _underlying.Contains(item);
}
public bool ContainsKey(TK key) {
return _underlying.ContainsKey(key);
}
public IEnumerator<KeyValuePair<TK, TD>> GetEnumerator() {
return _underlying.GetEnumerator();
}
public bool Remove(TK key) {
return _underlying.Remove(key);
}
public bool Remove(KeyValuePair<TK, TD> item) {
return _underlying.Remove(item.Key);
}
public bool TryGetValue(TK key, out TD value) {
return _underlying.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator() {
return _underlying.GetEnumerator();
}
}
Was ist mit diesem Einzeiler, der mit ContainsKey
prüft, ob ein Schlüssel vorhanden ist, und dann entweder den normal erhaltenen Wert oder den Standardwert mit dem bedingten Operator zurückgibt?
var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue;
Sie müssen keine neue Dictionary-Klasse implementieren, die Standardwerte unterstützt. Ersetzen Sie einfach Ihre Lookup-Anweisungen durch die kurze Zeile oben.