Menu

Dictionary C#: Add, TryGetValue, Iterasi, dan Sort

Dictionary<TKey, TValue> memetakan key ke value dengan pencarian yang cepat. Pelajari cara menambah dan memperbarui entri, membaca dengan aman lewat TryGetValue, melakukan iterasi pada entri KeyValuePair, menghitung kemunculan, mengabaikan huruf besar-kecil pada key, dan menjaga key tetap terurut dengan SortedDictionary.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Dictionary<TKey, TValue> menyimpan value di bawah key yang unik dan menemukan sebuah value berdasarkan key-nya dalam waktu yang kurang lebih konstan, berapa pun jumlah entrinya. Ini padanan hash map di C#: buku telepon dari nama ke nomor, cache dari ID ke record, atau jumlah kemunculan per kata.

Membuat dictionary dan membaca value

Output:

12
2.50
2
True
False

Kedua bentuk initializer melakukan hal yang sama. Bentuk ["key"] = value (C# 6) memakai indexer, jadi key yang berulang akan menimpa; bentuk { key, value } memanggil Add, jadi key yang berulang melempar exception saat baris itu dijalankan.

ContainsKey adalah pencarian hash dan cepat. ContainsValue harus memindai setiap entri, karena value tidak diindeks.

Add vs indexer vs TryAdd

Ada tiga cara untuk memasukkan entri, dan bedanya hanya pada apa yang terjadi saat key-nya sudah ada:

Output:

26
Caught ArgumentException
True
False
31

Add yang melempar exception untuk key duplikat adalah sebuah fitur: hal ini memunculkan data yang seharusnya unik tetapi ternyata tidak. Pakai indexer saat maksudmu "sisipkan atau perbarui", dan TryAdd (.NET Core 2.0 ke atas) saat value pertama yang harus menang.

Key tidak boleh null. Add(null, ...) atau dict[null] melempar ArgumentNullException. Value boleh null kalau tipe value-nya mengizinkan.

KeyNotFoundException dan TryGetValue

Membaca key yang tidak ada dengan indexer melempar KeyNotFoundException. Ini error dictionary yang paling umum, dan solusinya hampir selalu TryGetValue.

Output:

Caught KeyNotFoundException
Found ana@example.com
Missing, value is null: True
no email

TryGetValue melakukan satu kali pencarian hash dan melaporkan keberhasilannya sebagai bool. Pola if (dict.ContainsKey(k)) { var v = dict[k]; } memang berfungsi, tetapi mencari key-nya dua kali. Saat key-nya tidak ada, variabel out mendapat nilai default tipenya (null, 0, false).

Di .NET Core 2.0 ke atas juga ada GetValueOrDefault(key, fallback), yang mengembalikan nilai cadangan saat key-nya tidak ada: emails.GetValueOrDefault(103, "no email").

Memperbarui dan menghapus entri

Output:

2
True
False
1
0

cart["milk"] += 1 melempar KeyNotFoundException kalau milk belum ada di dictionary, karena operasi ini membaca dulu sebelum menulis. Remove mengembalikan false alih-alih melempar exception untuk key yang tidak ada, jadi tidak perlu mengecek ContainsKey lebih dulu.

Iterasi: KeyValuePair, Keys, dan Values

foreach pada dictionary menghasilkan item KeyValuePair<TKey, TValue>, masing-masing dengan Key dan Value.

Output:

Ana: 88
Ben: 72
Chloe: 95
Ana Ben Chloe 
Total 255
77

pair.Value bersifat read-only, jadi memperbarui value berarti menulis lewat indexer. Loop terakhir melakukan iterasi pada salinan key berupa List<string>, yang selalu aman; iterasi langsung pada scores.Keys sambil menimpa value yang sudah ada diizinkan di .NET Core 3.0 ke atas, tetapi melempar InvalidOperationException di .NET Framework.

Menambahkan key baru di dalam foreach pada dictionary yang sama melempar InvalidOperationException di semua versi. Menghapus selama iterasi melempar exception di .NET Framework dan diizinkan sejak .NET Core 3.0. Kode yang harus berjalan di mana saja mengumpulkan key yang akan dihapus lebih dulu, lalu menghapusnya setelah loop.

Dengan C# 7 dan .NET Core 2.0 ke atas, KeyValuePair bisa di-deconstruct di header loop:

foreach (var (name, score) in scores)
{
    Console.WriteLine($"{name}: {score}");
}

Menghitung dengan dictionary

Menghitung kemunculan adalah penggunaan klasiknya. Baca jumlah saat ini dengan TryGetValue (key yang tidak ada memberi 0), lalu tulis kembali.

Output:

the 3
cat 1
and 2
dog 1
bird 1

Bentuk yang sama juga bisa mengelompokkan item: Dictionary<string, List<Order>>, saat kamu mengambil list-nya dengan TryGetValue, membuat dan menyimpan list baru kalau belum ada, lalu memanggil Add padanya. Untuk menghitung dan mengelompokkan sekali jalan, LINQ melakukannya dalam satu ekspresi: words.GroupBy(w => w).ToDictionary(g => g.Key, g => g.Count()). Lihat LINQ.

Key yang tidak peka huruf besar-kecil dengan comparer

Secara default, key string dibandingkan secara persis: "Apple" dan "apple" adalah dua key. Berikan IEqualityComparer<string> ke constructor untuk mengubahnya.

Output:

False
text/html
1

StringComparer.OrdinalIgnoreCase adalah pilihan yang tepat untuk identifier seperti header HTTP, ekstensi file, dan username. Memanggil .ToLower() pada setiap key sebelum menyimpannya juga berfungsi, tetapi mudah terlupa di satu tempat.

Untuk key berupa class buatanmu sendiri, dictionary memanggil GetHashCode dan Equals milik key itu. Class yang tidak meng-override keduanya dibandingkan berdasarkan reference, jadi dua object terpisah dengan field yang sama dianggap key yang berbeda. Lihat HashSet untuk cara menulis pasangan method itu.

Urutan, pengurutan, dan SortedDictionary

Dictionary tidak menjanjikan urutan iterasi apa pun. Dalam praktiknya, dictionary yang hanya pernah ditambahi entri diiterasi sesuai urutan penambahan, tetapi setelah Remove, Add berikutnya bisa memakai ulang slot yang kosong dan muncul lebih awal. Kode tidak boleh bergantung pada hal ini.

Saat kamu butuh urutan, urutkan di tempat data dipakai atau pakai koleksi yang terurut:

Output:

Cairo 210
Lima 340
Oslo 520
By value, highest first:
Oslo 520
Lima 340
Cairo 210
Berlin, Cairo, Lima, Oslo

SortedDictionary<TKey, TValue> menjaga key-nya selalu terurut (strukturnya balanced tree), jadi pencarian dan penyisipan bernilai O(log n), bukan O(1). Pakai saat kamu sering melakukan iterasi berdasarkan urutan key; urutkan dictionary biasa dengan LINQ kalau kamu hanya butuh urutan sekali. SortedList<TKey, TValue> adalah pilihan ketiga yang memakai lebih sedikit memori, tetapi lambat untuk penyisipan saat ukurannya besar.

Referensi singkat

TugasKode
Membuatnew Dictionary<string, int>()
Menyisipkan atau menimpad[k] = v
Menyisipkan, exception kalau duplikatd.Add(k, v)
Menyisipkan hanya kalau barud.TryAdd(k, v)
Membaca, exception kalau tidak adad[k]
Membaca dengan amand.TryGetValue(k, out var v)
Key adad.ContainsKey(k)
Menghapusd.Remove(k) (mengembalikan bool)
Ukurand.Count
Key, valued.Keys, d.Values
Terurut berdasarkan keyd.OrderBy(p => p.Key) atau SortedDictionary
Abaikan huruf besar-kecilnew Dictionary<string, T>(StringComparer.OrdinalIgnoreCase)

Kesalahan umum

  • Membaca key yang tidak ada dengan d[k]. Melempar KeyNotFoundException; pakai TryGetValue.
  • Memanggil Add untuk key yang mungkin sudah ada. Melempar ArgumentException; pakai indexer atau TryAdd.
  • Menambahkan key di dalam foreach pada dictionary itu. Melempar InvalidOperationException; kumpulkan perubahannya dan terapkan setelahnya.
  • Bergantung pada urutan iterasi. Urutkan, atau pakai SortedDictionary.
  • Mengubah field object key setelah menyisipkannya. Hash code-nya berubah dan entrinya tidak bisa ditemukan lagi.
  • ContainsKey lalu indexer. Dua kali pencarian; TryGetValue cukup sekali.

Pertanyaan yang sering diajukan

Apa bedanya Dictionary.Add dan indexer di C#?

dict.Add(key, value) menyisipkan entri baru dan melempar ArgumentException kalau key-nya sudah ada. dict[key] = value menyisipkan entri kalau key-nya baru dan menimpa value-nya kalau sudah ada, dan tidak pernah melempar exception untuk key duplikat. TryAdd(key, value) hanya menyisipkan saat key-nya baru dan mengembalikan false kalau tidak.

Bagaimana TryGetValue bekerja di C#?

dict.TryGetValue(key, out var value) mengembalikan true dan mengisi value saat key-nya ada, serta mengembalikan false dan mengisi value dengan nilai default tipenya saat key-nya tidak ada. Method ini melakukan satu kali pencarian, sedangkan ContainsKey yang diikuti dict[key] melakukan dua kali, dan tidak pernah melempar KeyNotFoundException.

Bagaimana cara melakukan iterasi pada Dictionary di C#?

foreach (KeyValuePair<string, int> pair in dict) memberikan setiap entri dengan pair.Key dan pair.Value. Untuk perulangan pada key saja atau value saja, pakai dict.Keys atau dict.Values. Jangan menambahkan key ke dictionary di dalam loop itu: hal itu melempar InvalidOperationException.

Apakah Dictionary di C# berurutan?

Tidak ada urutan yang dijamin. Dictionary yang hanya pernah ditambahi entri biasanya diiterasi sesuai urutan penambahan, tetapi setelah Remove, entri baru bisa mengisi slot yang kosong, sehingga urutannya berubah. Urutkan saat kamu butuh urutan: dict.OrderBy(p => p.Key), atau pakai SortedDictionary<TKey, TValue>, yang selalu diiterasi berdasarkan key.

Bagaimana cara membuat key Dictionary tidak peka huruf besar-kecil?

Berikan sebuah comparer ke constructor: new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase). Dengan begitu, "Apple" dan "apple" dianggap key yang sama untuk pencarian, Add, dan ContainsKey. Comparer-nya ditetapkan saat dictionary dibuat.

Apa itu KeyValuePair di C#?

KeyValuePair<TKey, TValue> adalah struct yang diberikan dictionary untuk setiap entri saat kamu melakukan iterasi padanya. Struct ini punya property Key dan Value yang read-only, jadi kamu tidak bisa mengubah entri lewat struct itu; tulis dict[pair.Key] = newValue sebagai gantinya (setelah loop, atau pada salinan key-nya).

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai