Eksik anahtar üzerine atmak yerine varsayılan değeri döndüren bir IDictionary uygulaması var mı?


129

Sözlüğe dizin oluşturucu, anahtar eksikse bir istisna atar. Bunun yerine varsayılan (T) döndürecek bir IDictionary uygulaması var mı?

"TryGetValue" yöntemini biliyorum, ancak linq ile kullanılması imkansız.

Bu, ihtiyacım olanı verimli bir şekilde yapar mı ?:

myDict.FirstOrDefault(a => a.Key == someKeyKalue);

Hash araması kullanmak yerine anahtarları yineleyeceğini düşündüğüm için olacağını sanmıyorum.


10
Ayrıca, bunun bir kopyası olarak işaretlenmiş, ancak farklı yanıtlarla işaretlenmiş bu sonraki soruya da bakın: Anahtar yoksa sözlük varsayılan bir değer döndürür
Rory O'Kane

2
@dylan Bu, null değil, eksik anahtar için bir istisna oluşturacaktır. Ek olarak, sözlüğün kullanıldığı her yerde varsayılanın ne olması gerektiğine karar vermeyi gerektirir. Ayrıca bu sorunun yaşına da dikkat edin. Bizde yoktu? operatör 8 yıl önce anyhow
TheSoftwareJedi

Yanıtlar:


147

Aslında, bu hiç verimli olmayacak.

Her zaman bir uzantı yöntemi yazabilirsiniz:

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;
}

Veya C # 7.1 ile:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key) =>
    dictionary.TryGetValue(key, out var ret) ? ret : default;

Bu kullanır:

  • İfade gövdeli bir yöntem (C # 6)
  • Bir çıkış değişkeni (C # 7.0)
  • Varsayılan bir değişmez (C # 7.1)

2
Veya daha kısaca:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck

33
@PeterGluck: Daha kompakt, ancak daha az verimli ... Anahtar varken neden iki arama yapasınız ki?
Jon Skeet

5
@JonSkeet: Peter'ı düzelttiğiniz için teşekkürler; Bir süredir farkına varmadan bu "daha az verimli" yöntemi kullanıyorum.
J Bryan Price

5
Çok hoş! Utanmaz bir şekilde intihal yapacağım - şey, çatallayın. ;)
Jon Coombs

3
Görünüşe göre MS System.Collections.Generic.CollectionExtensions, az önce denediğim ve orada olduğu için bunun eklenecek kadar iyi olduğuna karar verdi .
theMayer

19

Bu uzatma yöntemlerini kullanmak yardımcı olabilir ..

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

3
Son aşırı yükleme ilginç. Seçmek için hiçbir şey olmadığı için gelen , bu basitçe tembel değerlendirme şeklidir?
MEMark

1
@MEMark yes değer seçici sadece gerektiğinde çalıştırılır, bu yüzden bir bakıma tembel değerlendirme olarak söylenebilir, ancak Linq bağlamında olduğu gibi ertelenmiş yürütmesi değildir. Ancak buradaki tembel değerlendirme , faktör arasından seçim yapmak için hiçbir şeye bağlı değildir , elbette seçici yapabilirsiniz Func<K, V>.
nawfal

1
Bu üçüncü yöntem, kendi başına yararlı olduğu için halka açık mı? Yani, birinin şu anki saati kullanan bir lambda veya ... hmm'ye dayalı bir hesaplama geçirebileceğini mi düşünüyorsunuz? Sadece ikinci yöntemin V değerini vermesini beklerdim; dönüş dict. TryGetValue (anahtar, çıkış değeri)? değer: defVal;
Jon Coombs

1
@JCoombs doğru, üçüncü aşırı yükleme aynı amaç için var. Ve tabii ki bunu ikinci aşırı yüklemede yapabilirsiniz, ama ben onu biraz kurutuyordum (böylece mantığı tekrar etmeyeyim).
nawfal

4
@nawfal İyi nokta. Kuru kalmak, cimri bir yöntem çağrısından kaçınmaktan daha önemlidir.
Jon Coombs

19

Eğer birisi .net core 2 ve üstü (C # 7.X) kullanıyorsa, CollectionExtensions sınıfı tanıtılır ve anahtar sözlükte yoksa varsayılan değeri almak için GetValueOrDefault yöntemini kullanabilir .

Dictionary<string, string> colorData = new  Dictionary<string, string>();
string color = colorData.GetValueOrDefault("colorId", string.Empty);

4

Collections.Specialized.StringDictionaryeksik bir anahtarın değerini ararken istisna olmayan bir sonuç sağlar. Ayrıca varsayılan olarak büyük / küçük harfe duyarlı değildir.

Uyarılar

Yalnızca özel kullanımları için geçerlidir ve - jeneriklerden önce tasarlandığı için - tüm koleksiyonu gözden geçirmeniz gerekiyorsa çok iyi bir numaralandırıcıya sahip değildir.


4

.Net Core kullanıyorsanız, CollectionExtensions.GetValueOrDefault yöntemini kullanabilirsiniz . Bu, kabul edilen cevapta verilen uygulama ile aynıdır.

public static TValue GetValueOrDefault<TKey,TValue> (
   this System.Collections.Generic.IReadOnlyDictionary<TKey,TValue> dictionary,
   TKey key);

3
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

Bir sözlüğün anahtar arama işlevi için bir arayüz tanımlanabilir. Muhtemelen şöyle tanımlardım:

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

Genel olmayan anahtarlara sahip sürüm, rastgele anahtar varyansına izin vermek için yapısal olmayan anahtar türlerini kullanan kodu kullanan koda izin verir; bu, genel bir tür parametresiyle mümkün değildir. İkincisi izin vereceği için Dictionary(Of Cat, String), bir değişkeni değişken olarak kullanmasına izin verilmemelidir . Ancak bir değişkeni değişmez olarak kullanmakta yanlış bir şey yoktur , çünkü mükemmel bir şekilde iyi biçimlendirilmiş bir ifade olarak düşünülmelidir ( sözlüğe, türde olmayan herhangi bir şey için arama yapmaya gerek kalmadan dönmelidir ).Dictionary(Of Animal, String)SomeDictionaryOfCat.Add(FionaTheFish, "Fiona")Dictionary(Of Cat, String)Dictionary(Of Animal, String)SomeDictionaryOfCat.Contains(FionaTheFish)falseCat

Ne yazık ki, böyle bir arabirimi fiilen kullanmanın tek yolu Dictionary, arabirimi uygulayan bir sınıfta bir nesneyi sarmalamaktır . Bununla birlikte, ne yaptığınıza bağlı olarak, böyle bir arayüz ve izin verdiği çeşitlilik, çabaya değebilir.


1

ASP.NET MVC kullanıyorsanız, işi yapan RouteValueDictionary sınıfından yararlanabilirsiniz.

public object this[string key]
{
  get
  {
    object obj;
    this.TryGetValue(key, out obj);
    return obj;
  }
  set
  {
    this._dictionary[key] = value;
  }
}

1

Bu soru, TryGetValueoyunlarınFirstOrDefault rolü .

Bahsetmek istediğim ilginç bir C # 7 özelliği, değişkenler özelliğidir ve eğer C # 6'dan boş koşullu operatörü denkleme eklerseniz, kodunuz fazladan uzatma yöntemlerine gerek kalmadan çok daha basit olabilir.

var dic = new Dictionary<string, MyClass>();
dic.TryGetValue("Test", out var item);
item?.DoSomething();

Bunun dezavantajı, her şeyi bu şekilde satır içi yapamayacağınızdır;

dic.TryGetValue("Test", out var item)?.DoSomething();

Bunu yapmak istiyorsak / yapmak istiyorsak, Jon'unki gibi bir uzantı yöntemini kodlamalıyız.


1

İşte C # 7.1 dünyası için @ JonSkeet'in isteğe bağlı bir varsayılanın da aktarılmasına izin veren bir sürümü:

public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue;

Geri dönmek istediğiniz durumu optimize etmek için iki işleve sahip olmak daha verimli olabilir default(TV):

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;
}

Ne yazık ki C # (henüz?) Virgül operatörüne (veya C # 6 önerilen noktalı virgül operatörüne) sahip değildir, bu nedenle aşırı yüklemelerden biri için gerçek bir işlev gövdesine (nefes al!) Sahip olmalısınız.


1

C ++ 'ya aşina olanlar için, STL haritasına çok benzer davranışa sahip bir IDictionary oluşturmak için kapsüllemeyi kullandım . Olmayanlar için:

  • Aşağıdaki SafeDictionary içindeki dizinleyici get {}, bir anahtar yoksa varsayılan değeri döndürür ve bu anahtarı varsayılan bir değerle sözlüğe ekler. Sonunda görünecek veya görünme şansı yüksek olan öğeleri ararken, bu genellikle istenen davranıştır.
  • yöntem Ekle (TK anahtarı, TV değeri) bir AddOrUpdate yöntemi gibi davranır ve varsa, mevcut değeri atmak yerine değiştirir. M $ 'ın neden bir AddOrUpdate yöntemi olmadığını ve çok yaygın senaryolarda hata atmanın iyi bir fikir olduğunu düşündüğünü anlamıyorum.

TL / DR - SafeDictionary, sapkın senaryolar dışında hiçbir koşulda istisna oluşturmayacak şekilde yazılmıştır. (veya yanması) gibi . Bunu, Ekle'yi AddOrUpdate davranışıyla değiştirerek ve dizinleyiciden NotFoundException atmak yerine varsayılanı döndürerek yapar.

İşte kod:

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

1

.NET core 2.0'dan beri şunları kullanabilirsiniz:

myDict.GetValueOrDefault(someKeyKalue)

0

Bir anahtarın mevcut olup olmadığını kontrol eden ContainsKeyve ardından koşullu işleci kullanarak normalde alınan değeri veya varsayılan değeri döndüren bu tek satırlık programa ne dersiniz ?

var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue;

Varsayılan değerleri destekleyen yeni bir Dictionary sınıfı uygulamaya gerek yoktur, arama ifadelerinizi yukarıdaki kısa satırla değiştirmeniz yeterlidir.


5
Bu, bir yerine iki arama içerir ve ConcurrentDictionary kullanıyorsanız bir yarış durumu sunar.
piedar

0

TryGetValueVarsa varsayılan değeri kontrol etmek ve döndürmek tek satırlık olabilir false.

 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

Çevrimiçi deneyin


-1

Hayır, çünkü aksi halde anahtar var olduğunda ancak boş bir değer sakladığında farkı nasıl anlarsınız? Bu önemli olabilir.


11
Olabilir - ancak bazı durumlarda bunun olmadığını iyi biliyor olabilirsiniz. Bunun bazen yararlı olduğunu görebiliyorum.
Jon Skeet

8
Boş değerin orada olmadığını biliyorsanız - buna sahip olmak son derece güzel. Linq'te bu şeylere katılmayı çok daha kolay hale getiriyor (son zamanlarda yaptığım tek şey bu)
TheSoftwareJedi

1
ContainsKey yöntemini kullanarak mı?
MattDavey

4
Evet, aslında çok kullanışlı. Python'da buna sahip ve Python'da olduğumda onu düz yöntemden çok daha fazla kullanıyorum. Yalnızca boş sonucu kontrol eden ifadeler için fazladan çok şey yazmayı kaydeder. (Haklısın, elbette, bu boş değer bazen işe yarıyor, ancak genellikle bunun yerine "" veya 0 istiyorum.)
Jon Coombs

Ben buna olumlu oy vermiştim. Ama bugün olsaydı yapmazdım. Şimdi iptal edemiyorum :). Söylediklerime yanıt verdim :)
nawfal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.