Apakah ada cara yang lebih baik untuk menggunakan kamus C # daripada TryGetValue?

19

Saya menemukan diri saya sering mencari pertanyaan online, dan banyak solusi termasuk kamus. Namun, setiap kali saya mencoba menerapkannya, saya mendapatkan bau yang mengerikan ini dalam kode saya. Misalnya setiap kali saya ingin menggunakan nilai:

int x;
if (dict.TryGetValue("key", out x)) {
    DoSomethingWith(x);
}

Itu 4 baris kode untuk dasarnya melakukan hal berikut: DoSomethingWith(dict["key"])

Saya pernah mendengar bahwa menggunakan kata kunci keluar adalah pola anti karena membuat fungsi mengubah parameternya.

Juga, saya menemukan diri saya sering membutuhkan kamus "terbalik", di mana saya membalikkan kunci dan nilai.

Demikian pula, saya sering ingin mengulang-ulang item dalam kamus dan menemukan diri saya mengubah kunci atau nilai ke daftar dll untuk melakukan ini dengan lebih baik.

Saya merasa hampir selalu ada cara kamus yang lebih baik, lebih elegan, tapi saya bingung.

Adam B
sumber
7
Cara lain mungkin ada tetapi saya biasanya menggunakan ContainsKey terlebih dahulu sebelum mencoba untuk mendapatkan nilai.
Robbie Dee
2
Jujur, dengan beberapa pengecualian, jika Anda tahu apa kunci kamus Anda dari waktu ke depan, mungkin adalah cara yang lebih baik. Jika Anda tidak tahu, kunci dikt umumnya tidak boleh dikodekan sama sekali. Kamus berguna untuk bekerja dengan objek atau data semi-terstruktur di mana bidang-bidangnya paling tidak sebagian besar ortogonal terhadap aplikasi. IMO, semakin dekat bidang-bidang itu dengan konsep bisnis / domain yang relevan, kamus menjadi kurang bermanfaat untuk bekerja dengan konsep-konsep tersebut.
svidgen
6
@RobbieDee: Anda harus berhati-hati ketika melakukan itu, karena hal itu menciptakan kondisi balapan. Mungkin kunci dapat dihapus antara memanggil ContainsKey dan mendapatkan nilai.
whatsisname
12
@whatsisname: Di bawah kondisi itu, sebuah ConcurrentDictionary akan lebih cocok. Koleksi di System.Collections.Genericnamespace tidak aman-utas .
Robert Harvey
4
50% bagus saat saya melihat seseorang menggunakan Dictionary, yang benar-benar mereka inginkan adalah kelas baru. Bagi mereka yang 50% (hanya), ini bau desain.
BlueRaja - Danny Pflughoeft

Jawaban:

23

Kamus (C # atau lainnya) hanyalah sebuah wadah tempat Anda mencari nilai berdasarkan kunci. Dalam banyak bahasa, ini lebih tepat diidentifikasi sebagai Peta dengan implementasi yang paling umum adalah HashMap.

Masalah yang harus dipertimbangkan adalah apa yang terjadi ketika kunci tidak ada. Beberapa bahasa berperilaku dengan mengembalikan nullatau nilatau nilai setara lainnya. Secara diam-diam menetapkan suatu nilai alih-alih memberi tahu Anda bahwa suatu nilai tidak ada.

Baik atau buruk, perancang perpustakaan C # menghasilkan idiom untuk mengatasi perilaku tersebut. Mereka beralasan bahwa perilaku default untuk mencari nilai yang tidak ada adalah dengan melemparkan pengecualian. Jika Anda ingin menghindari pengecualian, maka Anda dapat menggunakan Tryvarian. Ini adalah pendekatan yang sama yang mereka gunakan untuk mem-parsing string ke integer atau objek tanggal / waktu. Intinya, dampaknya seperti ini:

T count = int.Parse("12T45"); // throws exception

if (int.TryParse("12T45", out count))
{
    // Does not throw exception
}

Dan itu dibawa ke kamus, yang pengindeks delegasinya ke Get(index):

var myvalue = dict["12345"]; // throws exception
myvalue = dict.Get("12345"); // throws exception

if (dict.TryGet("12345", out myvalue))
{
    // Does not throw exception
}

Ini hanyalah cara bahasa dirancang.


Haruskah outvariabel tidak disarankan?

C # bukan bahasa pertama yang memilikinya, dan mereka memiliki tujuan mereka dalam situasi tertentu. Jika Anda mencoba membangun sistem yang sangat konkuren, maka Anda tidak bisa menggunakan outvariabel di batas konkurensi.

Dalam banyak hal, jika ada idiom yang didukung oleh penyedia perpustakaan bahasa dan inti, saya mencoba untuk mengadopsi idiom-idiom itu di API saya. Itu membuat API terasa lebih konsisten dan betah dalam bahasa itu. Jadi metode yang ditulis dalam Ruby tidak akan terlihat seperti metode yang ditulis dalam C #, C, atau Python. Mereka masing-masing memiliki cara pembuatan kode yang disukai, dan bekerja dengan itu membantu pengguna API Anda mempelajarinya lebih cepat.


Apakah Peta Secara Umum Anti-pola?

Mereka memiliki tujuan mereka, tetapi sering kali mereka mungkin menjadi solusi yang salah untuk tujuan yang Anda miliki. Terutama jika Anda memiliki pemetaan dua arah yang Anda butuhkan. Ada banyak wadah dan cara mengatur data. Ada banyak pendekatan yang dapat Anda gunakan, dan kadang-kadang Anda perlu berpikir sedikit sebelum memilih wadah itu.

Jika Anda memiliki daftar nilai pemetaan dua arah yang sangat singkat, maka Anda mungkin hanya perlu daftar tupel. Atau daftar struct, di mana Anda dapat dengan mudah menemukan kecocokan pertama di kedua sisi pemetaan.

Pikirkan domain masalah, dan pilih alat yang paling tepat untuk pekerjaan itu. Jika tidak ada, maka buatlah.

Berin Loritsch
sumber
4
"maka kamu mungkin hanya perlu daftar tupel. Atau daftar struct, di mana kamu dapat dengan mudah menemukan kecocokan pertama di kedua sisi pemetaan." - Jika ada optimisasi untuk set kecil, mereka harus dibuat di perpustakaan, bukan dicap karet ke dalam kode pengguna.
Blrfl
Jika Anda memilih daftar tupel atau kamus, itu adalah detail implementasi. Ini adalah pertanyaan untuk memahami domain masalah Anda dan menggunakan alat yang tepat untuk pekerjaan itu. Jelas, daftar ada dan kamus ada. Untuk kasus umum, kamus sudah benar, tetapi mungkin untuk satu atau dua aplikasi Anda perlu menggunakan daftar.
Berin Loritsch
3
Memang, tetapi jika perilakunya masih sama, tekad itu harus di perpustakaan. Saya telah menjalankan implementasi kontainer dalam bahasa lain yang akan berganti algoritma jika diberi tahu apriori bahwa jumlah entri akan kecil. Saya setuju dengan jawaban Anda, tetapi setelah ini sudah dipecahkan, itu harus di perpustakaan, mungkin sebagai SmallBidirectionalMap.
Blrfl
5
"Lebih baik atau lebih buruk, para desainer perpustakaan C # datang dengan idiom untuk menangani perilaku itu." - Saya pikir Anda menekan paku di kepala: mereka "muncul" dengan idiom, alih-alih menggunakan yang sudah banyak digunakan, yang merupakan jenis pengembalian opsional.
Jörg W Mittag
3
@ JörgWMittag Jika Anda berbicara tentang sesuatu seperti Option<T>, maka saya pikir itu akan membuat metode ini lebih sulit untuk digunakan di C # 2.0, karena tidak memiliki pencocokan pola.
svick
21

Beberapa jawaban bagus di sini tentang prinsip umum dari hashtable / kamus. Tapi saya pikir saya akan menyentuh contoh kode Anda,

int x;
if (dict.TryGetValue("key", out x)) 
{
    DoSomethingWith(x);
}

Pada C # 7 (yang saya pikir berusia sekitar dua tahun), yang dapat disederhanakan menjadi:

if (dict.TryGetValue("key", out var x))
{
    DoSomethingWith(x);
}

Dan tentu saja dapat dikurangi menjadi satu baris:

if (dict.TryGetValue("key", out var x)) DoSomethingWith(x);

Jika Anda memiliki nilai default untuk saat kunci tidak ada, itu bisa menjadi:

DoSomethingWith(dict.TryGetValue("key", out var x) ? x : defaultValue);

Jadi, Anda dapat mencapai formulir ringkas dengan menggunakan penambahan bahasa yang cukup baru.

David Arno
sumber
1
Panggilan bagus pada sintaks v7, opsi kecil yang bagus untuk harus memotong garis definisi tambahan sambil memungkinkan penggunaan var +1
BrianH
2
Perhatikan juga bahwa jika "key"tidak ada, xakan diinisialisasi kedefault(TValue)
Peter Duniho
Mungkin lebih baik sebagai ekstensi generik, juga dipanggil seperti"key".DoSomethingWithThis()
Ross Presser
Saya sangat suka desain yang menggunakan getOrElse("key", defaultValue) objek Null masih pola favorit saya. Kerjakan seperti itu dan Anda tidak peduli apakah TryGetValuemengembalikan benar atau salah.
candied_orange
1
Saya merasa seperti jawaban ini pantas untuk disanggah bahwa menulis kode ringkas untuk kepentingannya itu buruk. Ini bisa membuat kode Anda lebih sulit dibaca. Selain itu, jika saya ingat dengan benar, TryGetValuetidak aman untuk atom / thread sehingga Anda dapat dengan mudah melakukan satu pemeriksaan untuk keberadaan dan lainnya untuk mengambil dan beroperasi pada nilai
Marie
12

Ini bukan bau kode atau anti-pola, karena menggunakan fungsi gaya TryGet dengan parameter keluar adalah C # idiomatik. Namun, ada 3 opsi yang disediakan dalam C # untuk bekerja dengan Kamus, jadi sebaiknya Anda yakin Anda menggunakan yang benar untuk situasi Anda. Saya rasa saya tahu dari mana rumor bahwa ada masalah menggunakan parameter out berasal, jadi saya akan mengatasinya pada akhirnya.

Fitur apa yang digunakan ketika bekerja dengan Kamus C #:

  1. Jika Anda yakin kuncinya ada di Kamus, gunakan properti Item [TKey]
  2. Jika kuncinya pada umumnya ada di Kamus, tetapi itu buruk / jarang / bermasalah bahwa itu tidak ada, Anda harus menggunakan Coba ... Tangkap sehingga kesalahan akan dinaikkan dan kemudian Anda dapat mencoba untuk menangani kesalahan anggun
  3. Jika Anda tidak yakin kunci akan ada di Kamus, gunakan TryGet dengan parameter keluar

Untuk membenarkan ini, orang hanya perlu merujuk ke dokumentasi untuk Kamus TryGetValue, di bawah "Keterangan" :

Metode ini menggabungkan fungsionalitas metode ContainsKey dan properti Item [TKey].

...

Gunakan metode TryGetValue jika kode Anda sering mencoba mengakses kunci yang tidak ada dalam kamus. Menggunakan metode ini lebih efisien daripada menangkap KeyNotFoundException yang dilemparkan oleh properti Item [TKey].

Metode ini mendekati operasi O (1).

Seluruh alasan TryGetValue ada adalah untuk bertindak sebagai cara yang lebih nyaman untuk menggunakan ContainsKey dan Item [TKey], sambil menghindari keharusan mencari kamus dua kali - jadi berpura-pura tidak ada dan melakukan dua hal yang dilakukan secara manual agak canggung pilihan.

Dalam praktiknya, saya jarang menggunakan Kamus mentah, karena pepatah sederhana ini: pilih kelas / wadah paling umum yang memberi Anda fungsionalitas yang Anda butuhkan . Kamus tidak dirancang untuk mencari berdasarkan nilai daripada dengan kunci (misalnya), jadi jika itu adalah sesuatu yang Anda inginkan, mungkin lebih masuk akal untuk menggunakan struktur alternatif. Saya pikir saya mungkin telah menggunakan Kamus satu kali dalam proyek pembangunan selama setahun terakhir yang saya lakukan, hanya karena itu jarang alat yang tepat untuk pekerjaan yang saya coba lakukan. Kamus jelas bukan pisau Tentara Swiss dari kotak alat C #.

Apa yang salah dengan parameter keluar?

CA1021: Hindari parameter

Meskipun nilai kembali adalah hal yang biasa dan banyak digunakan, penerapan parameter out dan ref yang benar membutuhkan keterampilan desain dan pengkodean menengah. Arsitek perpustakaan yang mendesain untuk khalayak umum seharusnya tidak mengharapkan pengguna untuk menguasai bekerja tanpa parameter atau ref.

Saya menduga di situlah Anda mendengar bahwa parameter keluar adalah sesuatu seperti anti-pola. Seperti halnya semua aturan, Anda harus membaca lebih dekat untuk memahami 'mengapa', dan dalam kasus ini bahkan ada penyebutan eksplisit tentang bagaimana pola Coba tidak melanggar aturan :

Metode yang menerapkan pola Coba, seperti System.Int32.TryParse, jangan ajukan pelanggaran ini.

BrianH
sumber
Seluruh daftar panduan itu adalah sesuatu yang saya harap akan dibaca lebih banyak orang. Bagi yang mengikutinya, inilah akarnya. docs.microsoft.com/en-us/visualstudio/code-quality/…
Peter Wone
10

Setidaknya ada dua metode yang hilang dari kamus C # yang menurut saya membersihkan kode jauh dalam banyak situasi dalam bahasa lain. Yang pertama adalah mengembalikan sebuahOption , yang memungkinkan Anda menulis kode seperti yang berikut di Scala:

dict.get("key").map(doSomethingWith)

Yang kedua adalah mengembalikan nilai default yang ditentukan pengguna jika kunci tidak ditemukan:

doSomethingWith(dict.getOrElse("key", "key not found"))

Ada sesuatu yang bisa dikatakan untuk menggunakan idiom yang disediakan bahasa saat yang tepat, seperti Trypolanya, tetapi itu tidak berarti Anda harus hanya menggunakan apa yang disediakan bahasa. Kami adalah programmer. Tidak apa-apa untuk membuat abstraksi baru untuk membuat situasi khusus kita lebih mudah dipahami, terutama jika itu menghilangkan banyak pengulangan. Jika Anda sering membutuhkan sesuatu, seperti membalikkan pencarian atau mengulangi nilai-nilai, mewujudkannya. Buat antarmuka yang Anda inginkan.

Karl Bielefeldt
sumber
Saya suka opsi ke-2. Mungkin saya harus menulis satu atau dua metode ekstensi :)
Adam B
2
di sisi positifnya c # metode ekstensi berarti Anda dapat menerapkannya sendiri
jk.
5

Itu 4 baris kode untuk dasarnya melakukan hal berikut: DoSomethingWith(dict["key"])

Saya setuju bahwa ini tidak berlaku. Mekanisme yang saya suka gunakan dalam kasus ini, di mana nilainya adalah tipe struct, adalah:

public static V? TryGetValue<K, V>(
      this Dictionary<K, V> dict, K key) where V : struct => 
  dict.TryGetValue(key, out V v)) ? new V?(v) : new V?();

Sekarang kami memiliki versi baru dari TryGetValuepengembalian itu int?. Kami kemudian dapat melakukan trik serupa untuk memperluas T?:

public static void DoIt<T>(
      this T? item, Action<T> action) where T : struct
{
  if (item != null) action(item.GetValueOrDefault());
}

Dan sekarang kumpulkan:

dict.TryGetValue("key").DoIt(DoSomethingWith);

dan kita sampai pada satu pernyataan yang jelas.

Saya pernah mendengar bahwa menggunakan kata kunci keluar adalah pola anti karena membuat fungsi mengubah parameternya.

Saya akan sedikit kurang kuat mengatakan, dan mengatakan bahwa menghindari mutasi bila memungkinkan adalah ide yang baik.

Saya menemukan diri saya sering membutuhkan kamus "terbalik", di mana saya membalikkan kunci dan nilai.

Kemudian implementasikan atau dapatkan kamus dua arah. Mereka mudah untuk menulis, atau ada banyak implementasi yang tersedia di internet. Ada banyak implementasi di sini, misalnya:

/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp

Demikian pula, saya sering ingin mengulang-ulang item dalam kamus dan menemukan diri saya mengubah kunci atau nilai ke daftar dll untuk melakukan ini dengan lebih baik.

Tentu, kita semua melakukannya.

Saya merasa hampir selalu ada cara kamus yang lebih baik, lebih elegan, tapi saya bingung.

Tanyai dirimu sendiri, "seandainya aku punya kelas selain Dictionaryyang mengimplementasikan operasi tepat yang ingin aku lakukan; seperti apa kelas itu?" Kemudian, setelah Anda menjawab pertanyaan itu, terapkan kelas itu . Anda seorang programmer komputer. Selesaikan masalah Anda dengan menulis program komputer!

Eric Lippert
sumber
Terima kasih. Apakah Anda pikir Anda bisa menjelaskan sedikit lebih banyak tentang apa yang sebenarnya dilakukan metode tindakan? Saya baru dalam bertindak.
Adam B
1
@AdamB suatu tindakan adalah objek yang mewakili kemampuan untuk memanggil metode void. Seperti yang Anda lihat, di sisi penelepon kami memberikan nama metode void yang daftar parameternya cocok dengan argumen tipe umum tindakan. Di sisi penelepon tindakan dipanggil seperti metode lainnya.
Eric Lippert
1
@ AdamB: Anda dapat menganggapnya Action<T>sangat mirip interface IAction<T> { void Invoke(T t); }, tetapi dengan aturan yang sangat lunak tentang apa yang "mengimplementasikan" antarmuka itu, dan tentang bagaimana Invokemungkin dipanggil. Jika Anda ingin tahu lebih banyak, pelajari tentang "delegasi" dalam C #, dan kemudian pelajari tentang ekspresi lambda.
Eric Lippert
Ok saya pikir saya mengerti. Jadi tindakannya mari kita menempatkan metode sebagai parameter untuk DoIt (). Dan menyebut metode itu.
Adam B
@ AdBB: Tepat sekali. Ini adalah contoh "pemrograman fungsional dengan fungsi tingkat tinggi". Sebagian besar fungsi mengambil data sebagai argumen; fungsi tingkat tinggi mengambil fungsi sebagai argumen dan kemudian melakukan hal-hal dengan fungsi tersebut. Ketika Anda belajar lebih banyak C # Anda akan melihat bahwa LINQ sepenuhnya diimplementasikan dengan fungsi tingkat tinggi.
Eric Lippert
4

The TryGetValue()membangun hanya diperlukan jika Anda tidak tahu apakah "kunci" hadir sebagai kunci dalam kamus atau tidak, jika tidak DoSomethingWith(dict["key"])benar-benar berlaku.

Pendekatan "kurang kotor" mungkin digunakan ContainsKey()sebagai cek.

Dr luar negeri
sumber
6
"Pendekatan" kurang kotor "mungkin menggunakan ContainsKey () sebagai centang." Saya tidak setuju. TryGetValueMeskipun tidak optimal , setidaknya itu membuatnya sulit untuk lupa menangani kasing kosong. Idealnya, saya berharap ini hanya akan mengembalikan Opsional.
Alexander - Pasang kembali Monica
1
Ada masalah potensial dengan ContainsKeypendekatan untuk aplikasi multithreaded, yang merupakan waktu pemeriksaan terhadap waktu penggunaan (TOCTOU) kerentanan. Bagaimana jika beberapa utas lainnya menghapus kunci antara panggilan ke ContainsKeydan GetValue?
David Hammen
2
@JAD Opsional membuat. Anda dapat memilikiOptional<Optional<T>>
Alexander - Reinstate Monica
2
@DavidHammen Untuk menjadi jelas, Anda akan perlu TryGetValuedi ConcurrentDictionary. Kamus reguler tidak akan disinkronkan
Alexander - Reinstate Monica
2
@ JAD Tidak, (pukulan opsional Java, jangan mulai saya). Saya berbicara lebih abstrak
Alexander - Reinstate Monica
2

Jawaban lain berisi poin-poin bagus, jadi saya tidak akan menyatakannya kembali di sini, tetapi saya akan fokus pada bagian ini, yang sejauh ini tampaknya diabaikan:

Demikian pula, saya sering ingin mengulang-ulang item dalam kamus dan menemukan diri saya mengubah kunci atau nilai ke daftar dll untuk melakukan ini dengan lebih baik.

Sebenarnya, cukup mudah untuk beralih ke kamus, karena kamus ini mengimplementasikan IEnumerable:

var dict = new Dictionary<int, string>();

foreach ( var item in dict ) {
    Console.WriteLine("{0} => {1}", item.Key, item.Value);
}

Jika Anda lebih suka Linq, itu juga berhasil:

dict.Select(x=>x.Value).WhateverElseYouWant();

Secara keseluruhan, saya tidak menemukan Kamus sebagai antipattern - mereka hanya alat khusus dengan kegunaan khusus.

Juga, untuk lebih spesifik, periksa SortedDictionary(menggunakan pohon RB untuk kinerja yang lebih dapat diprediksi) dan SortedList(yang juga merupakan kamus yang membingungkan yang mengorbankan kecepatan penyisipan untuk kecepatan pencarian, tetapi bersinar jika Anda menatap dengan tampilan tetap, pra set diurutkan). Saya punya kasus di mana mengganti Dictionarydengan SortedDictionarymenghasilkan urutan besarnya eksekusi lebih cepat (tapi itu bisa terjadi sebaliknya juga).

Vilx-
sumber
1

Jika Anda merasa menggunakan kamus itu aneh, itu mungkin bukan pilihan yang tepat untuk masalah Anda. Kamus sangat bagus tetapi seperti yang diperhatikan oleh seorang komentator, seringkali kamus digunakan sebagai jalan pintas untuk sesuatu yang seharusnya merupakan kelas. Atau kamus itu sendiri mungkin benar sebagai metode penyimpanan inti tetapi harus ada kelas pembungkus di sekitarnya untuk menyediakan metode layanan yang diinginkan.

Banyak yang telah dikatakan tentang Dict [kunci] versus TryGet. Apa yang saya gunakan banyak adalah iterasi kamus menggunakan KeyValuePair. Rupanya ini adalah konstruksi yang kurang dikenal.

Manfaat utama kamus adalah sangat cepat dibandingkan dengan koleksi lain karena jumlah item bertambah. Jika Anda harus memeriksa apakah kunci ada banyak, Anda mungkin ingin bertanya pada diri sendiri apakah penggunaan kamus Anda sesuai. Sebagai klien Anda biasanya harus tahu apa yang Anda masukkan ke sana dan dengan demikian apa yang akan aman untuk ditanyakan.

Martin Maat
sumber