wake-up-neo.net

Gibt es eine IDictionary-Implementierung, die bei fehlendem Schlüssel den Standardwert zurückgibt, anstatt zu werfen

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.

102
TheSoftwareJedi

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:

  • Eine Methode mit Ausdruckskörper (C # 6)
  • Eine out-Variable (C # 7.0)
  • Ein Standardliteral (C # 7.1)
119
Jon Skeet

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();
}
17
nawfal

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.

Vorsichtsmaßnahmen

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.

3
Mark Hurd

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. 

1
cdev

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.

1
supercat

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.

1
hardkoded
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();
    }
}
1
Brian

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.

1
NetMage

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;
  }
}
1
Jone Polvora

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

Online ausprobieren

0
Aryan Firouzian

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:

  • der Indexer get {} in SafeDictionary unten gibt den Standardwert zurück, wenn kein Schlüssel vorhanden ist.undfügen diesen Schlüssel mit einem Standardwert zum Wörterbuch hinzu. Dies ist häufig das gewünschte Verhalten, da Sie nach Elementen suchen, die eventuell erscheinen oder gute Chancen haben, dass sie erscheinen.
  • die Methode Add (TK-Taste, TV-Wert) verhält sich wie eine AddOrUpdate-Methode und ersetzt den vorhandenen Wert, wenn er existiert, anstatt ihn zu werfen. Ich verstehe nicht, warum m $ keine AddOrUpdate-Methode hat und der Meinung ist, Fehler in sehr häufigen Szenarien zu werfen, ist eine gute Idee.

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();
    }
}
0
neural

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.

0
Byte Commander