Apakah ada implementasi IDictionary yang, pada kunci yang hilang, mengembalikan nilai default daripada melempar?


129

Pengindeks ke Kamus mengeluarkan pengecualian jika kunci hilang. Apakah ada implementasi IDictionary yang akan mengembalikan default (T)?

Saya tahu tentang metode "TryGetValue", tapi itu tidak mungkin untuk digunakan dengan LINQ.

Apakah ini secara efisien akan melakukan apa yang saya butuhkan ?:

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

Saya tidak berpikir itu akan karena saya pikir itu akan mengulangi kunci daripada menggunakan pencarian Hash.


10
Lihat juga pertanyaan ini nanti, yang ditandai sebagai duplikat dari pertanyaan ini, tetapi dengan jawaban yang berbeda: Kamus mengembalikan nilai default jika kunci tidak ada
Rory O'Kane

2
@dylan Itu akan membuang pengecualian pada kunci yang hilang, bukan nol. Selain itu akan membutuhkan memutuskan apa yang seharusnya ada di mana-mana kamus digunakan. Perhatikan juga usia pertanyaan ini. Kami tidak punya ?? Operator 8 tahun yang lalu
TheSoftwareJedi

Jawaban:


147

Memang, itu tidak akan efisien sama sekali.

Anda selalu bisa menulis metode ekstensi:

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

Atau dengan C # 7.1:

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

Itu menggunakan:

  • Metode ekspresi-tubuh (C # 6)
  • Variabel keluar (C # 7.0)
  • Literal default (C # 7.1)

2
Atau lebih ringkas:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck

33
@PeterGluck: Lebih kompak, tapi kurang efisien ... mengapa melakukan dua pencarian dalam kasus ketika kunci ada?
Jon Skeet

5
@ JonSkeet: Terima kasih telah mengoreksi Peter; Saya telah menggunakan metode "kurang efisien" tanpa menyadarinya untuk sementara waktu sekarang.
J Bryan Harga

5
Sangat bagus! Aku akan menjiplak tanpa malu - eh, bercabang. ;)
Jon Coombs

3
Rupanya MS memutuskan ini cukup baik untuk ditambahkan System.Collections.Generic.CollectionExtensions, karena saya baru saja mencoba dan ada di sana.
theMayer

19

Membawa metode ekstensi ini dapat membantu ..

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
Kelebihan terakhir menarik. Karena tidak ada untuk memilih dari , apakah ini hanyalah sebuah bentuk evaluasi malas?
MEMark

1
@Markark ya pemilih nilai dijalankan hanya jika diperlukan, jadi dengan cara itu dapat dikatakan sebagai evaluasi malas, tetapi pelaksanaannya tidak ditangguhkan seperti dalam konteks Linq. Tapi evaluasi malas di sini tidak tergantung pada apa pun untuk memilih dari faktor, Anda tentu saja dapat membuat pemilih Func<K, V>.
nawfal

1
Apakah metode ketiga itu bersifat publik karena berguna sendiri? Artinya, apakah Anda berpikir seseorang mungkin lulus lambda yang menggunakan waktu saat ini, atau perhitungan berdasarkan ... hmm. Saya akan berharap hanya memiliki metode kedua nilai V; return dict.TryGetValue (kunci, nilai keluar)? nilai: defVal;
Jon Coombs

1
@ Joomooms benar, kelebihan ketiga ada untuk tujuan yang sama. Dan ya tentu saja Anda bisa melakukan itu di overload kedua, tapi saya membuatnya agak kering (sehingga saya tidak mengulangi logika).
nawfal

4
@nawfal Poin bagus. Tetap kering lebih penting daripada menghindari satu panggilan metode sangat sedikit.
Jon Coombs

19

Jika seseorang menggunakan .net core 2 dan di atasnya (C # 7.X), kelas CollectionExtensions diperkenalkan dan dapat menggunakan metode GetValueOrDefault untuk mendapatkan nilai default jika kunci tidak ada dalam kamus.

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

4

Collections.Specialized.StringDictionarymemberikan hasil non-pengecualian saat mencari nilai kunci yang hilang. Ini juga case-insensitive secara default.

Peringatan

Ini hanya berlaku untuk penggunaan khusus, dan - dirancang sebelum obat generik - tidak memiliki enumerator yang sangat baik jika Anda perlu meninjau seluruh koleksi.


4

Jika Anda menggunakan .Net Core, Anda dapat menggunakan metode CollectionExtensions.GetValueOrDefault . Ini sama dengan implementasi yang disediakan dalam jawaban yang diterima.

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

Seseorang dapat mendefinisikan antarmuka untuk fungsi pencarian kunci kamus. Saya mungkin akan mendefinisikannya sebagai sesuatu seperti:

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

Versi dengan kunci non-generik akan memungkinkan kode yang menggunakan kode menggunakan tipe kunci non-struktur untuk memungkinkan varian kunci sewenang-wenang, yang tidak mungkin dilakukan dengan parameter tipe generik. Seseorang seharusnya tidak diizinkan untuk menggunakan yang bisa berubah Dictionary(Of Cat, String)sebagai yang bisa berubah Dictionary(Of Animal, String)karena yang terakhir akan memungkinkan SomeDictionaryOfCat.Add(FionaTheFish, "Fiona"). Tetapi tidak ada yang salah dengan menggunakan bisa berubah Dictionary(Of Cat, String)sebagai tidak berubah Dictionary(Of Animal, String), karena SomeDictionaryOfCat.Contains(FionaTheFish)harus dianggap sebagai ekspresi yang terbentuk dengan baik (harus kembali false, tanpa harus mencari kamus, untuk apa pun yang bukan tipe Cat).

Sayangnya, satu-satunya cara seseorang dapat benar-benar menggunakan antarmuka seperti itu adalah jika seseorang membungkus Dictionaryobjek di kelas yang mengimplementasikan antarmuka. Bergantung pada apa yang Anda lakukan, antarmuka seperti itu dan varians yang dibolehkan mungkin membuatnya sepadan.


1

Jika Anda menggunakan ASP.NET MVC, Anda bisa memanfaatkan kelas RouteValueDictionary yang melakukan pekerjaan itu.

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

1

Pertanyaan ini membantu mengonfirmasi bahwa TryGetValuedrama tersebutFirstOrDefault peran di sini.

Salah satu fitur C # 7 yang menarik yang ingin saya sebutkan adalah fitur variabel keluar , dan jika Anda menambahkan operator null-kondisional dari C # 6 ke persamaan kode Anda bisa jauh lebih sederhana tanpa perlu metode ekstensi tambahan.

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

Kelemahan dari ini adalah bahwa Anda tidak dapat melakukan semuanya sesuai seperti ini;

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

Jika kita perlu / ingin melakukan ini kita harus mengkode satu metode ekstensi seperti Jon.


1

Ini adalah versi dari @ JonSkeet untuk dunia C # 7.1 yang juga memungkinkan untuk melewatinya opsional opsional:

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

Mungkin lebih efisien untuk memiliki dua fungsi untuk mengoptimalkan kasing tempat Anda ingin kembali 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;
}

Sayangnya C # belum (belum?) Memiliki operator koma (atau operator titik koma C # 6 yang diusulkan) sehingga Anda harus memiliki badan fungsi aktual (terkesiap!) Untuk salah satu kelebihan beban.


1

Saya menggunakan enkapsulasi untuk membuat IDictionary dengan perilaku yang sangat mirip dengan peta STL , bagi Anda yang terbiasa dengan c ++. Bagi mereka yang tidak:

  • indexer dapatkan {} di SafeDictionary di bawah ini mengembalikan nilai default jika kunci tidak ada, dan menambahkan kunci itu ke kamus dengan nilai default. Ini sering merupakan perilaku yang diinginkan, karena Anda mencari item yang nantinya akan muncul atau memiliki peluang bagus untuk muncul.
  • metode Add (kunci TK, val TV) berperilaku sebagai metode AddOrUpdate, menggantikan nilai sekarang jika ada, bukan melempar. Saya tidak mengerti mengapa m $ tidak memiliki metode AddOrUpdate dan berpikir melempar kesalahan dalam skenario yang sangat umum adalah ide yang bagus.

TL / DR - SafeDictionary ditulis agar tidak pernah membuat pengecualian dalam keadaan apa pun, selain skenario sesat , seperti komputer kehabisan memori (atau terbakar). Ini dilakukan dengan mengganti Add dengan perilaku AddOrUpdate dan mengembalikan default alih-alih melempar NotFoundException dari pengindeks.

Berikut kodenya:

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

Sejak .NET core 2.0 Anda dapat menggunakan:

myDict.GetValueOrDefault(someKeyKalue)

0

Bagaimana dengan satu-liner ini yang memeriksa apakah kunci hadir menggunakan ContainsKeydan kemudian mengembalikan nilai yang biasanya diambil kembali atau nilai default menggunakan operator kondisional?

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

Tidak perlu menerapkan kelas Kamus baru yang mendukung nilai default, cukup ganti pernyataan pencarian Anda dengan garis pendek di atas.


5
Ini memiliki dua pencarian bukan satu dan memperkenalkan kondisi balapan jika Anda menggunakan ConcurrentDictionary.
piedar

0

Ini bisa menjadi satu-liner untuk memeriksa TryGetValuedan mengembalikan nilai default jika ya 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

Cobalah online


-1

Tidak, karena jika tidak, bagaimana Anda tahu bedanya ketika kunci ada tetapi menyimpan nilai nol? Itu bisa penting.


11
Bisa jadi - tetapi dalam beberapa situasi Anda mungkin tahu itu bukan. Saya bisa melihat ini berguna kadang-kadang.
Jon Skeet

8
Jika Anda tahu null tidak ada di sana - ini sangat bagus untuk dimiliki. Itu membuat bergabung dengan hal-hal ini di Linq (yang saya lakukan belakangan ini) jauh lebih mudah
TheSoftwareJedi

1
Dengan menggunakan metode ContainsKey?
MattDavey

4
Ya, ini sebenarnya sangat berguna. Python memiliki ini dan saya menggunakannya jauh lebih dari metode biasa, ketika saya kebetulan berada di Python. Menghemat menulis banyak tambahan jika pernyataan yang hanya memeriksa hasil nol. (Anda benar, tentu saja, nol itu sesekali berguna, tetapi biasanya saya menginginkan "" atau 0 sebagai gantinya.)
Jon Coombs

Saya telah membatalkan ini. Tetapi jika hari ini saya tidak akan melakukannya. Tidak dapat membatalkan sekarang :). Saya membenarkan komentar untuk menyatakan maksud saya :)
nawfal
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.