C # sözlüklerini kullanmanın TryGetValue'dan daha iyi bir yolu var mı?


19

Kendimi çevrimiçi olarak sık sık soru ararken buluyorum ve birçok çözüm sözlükler içeriyor. Ancak, bunları uygulamaya çalıştığımda, kodumda bu korkunç reek var. Örneğin, her değer kullanmak istediğimde:

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

Bu esasen aşağıdakileri yapmak için 4 kod satırıdır: DoSomethingWith(dict["key"])

Out anahtar kelime kullanarak bir anti-desen olduğunu duydum çünkü fonksiyonları parametrelerini mutasyon yapar.

Ayrıca, kendimi genellikle anahtarları ve değerleri çevirdiğim "tersine çevrilmiş" bir sözlüğe ihtiyaç duyuyorum.

Benzer şekilde, genellikle bir sözlükteki öğeleri yinelemek ve kendimi daha iyi yapmak için anahtarları veya değerleri listelere vb.

Sözlükleri kullanmanın neredeyse her zaman daha iyi, daha zarif bir yolu olduğunu hissediyorum, ama kaybım var.


7
Başka yollar var olabilir ama bir değer elde etmeye çalışmadan önce genellikle bir ContainsKey kullanın .
Robbie Dee

2
Eğer biliyorsanız Dürüst, birkaç istisna dışında, senin Sözlük tuşları vaktinden nelerdir, muhtemelen olan daha iyi bir yolu. Eğer bilmiyorsanız, dikte tuşları genellikle sabit kodlanmış olmamalıdır. Sözlükleri alanları en azından yarı-yapılı nesneler veya veri ile çalışmak için yararlı olan daha çok uygulamaya ortogonal. IMO, bu alanlar ilgili işletme / etki alanı kavramları haline geldikçe, bu kavramlarla çalışmak için sözlükler o kadar az yardımcı olur.
svidgen

6
@RobbieDee: Bunu yaparken dikkatli olmalısınız, çünkü bunu yapmak bir yarış durumu yaratır. ContainsKey'i çağırmak ve değeri almak arasında anahtarın kaldırılması mümkündür.
whatsisname

12
@whatsisname: Bu koşullar altında, bir ConcurrentDictionary daha uygun olacaktır. Ad System.Collections.Genericalanındaki koleksiyonlar iş parçacığı için güvenli değildir .
Robert Harvey

4
Birini kullanan birini gördüğümde% 50 iyi Dictionary, gerçekten istedikleri şey yeni bir sınıftı. Bu% 50 (sadece) için bir tasarım kokusu.
BlueRaja - Danny Pflughoeft

Yanıtlar:


23

Sözlükler (C # veya başka türlü), bir anahtara dayalı bir değer aradığınız bir kapsayıcıdır. Birçok dilde daha doğru bir Harita olarak tanımlanır ve en yaygın uygulama HashMap'tır.

Dikkate alınması gereken sorun, bir anahtar olmadığında ne olacağıdır. Bazı diller geri nullveya nilbaşka bir eşdeğer değerle davranır . Bir değerin var olmadığını bildirmek yerine sessizce bir değeri varsayılan olarak ayarlamak.

Daha iyi ya da kötü için, C # kütüphane tasarımcıları davranış ile başa çıkmak için bir deyim ortaya çıktı. Var olmayan bir değeri aramak için varsayılan davranışın bir istisna atmak olduğunu düşündüler. İstisnalardan kaçınmak istiyorsanız, Tryvaryantı kullanabilirsiniz . Dizeleri tamsayılara veya tarih / saat nesnelerine ayrıştırmak için kullandıkları yaklaşımla aynıdır. Temel olarak, etki şöyle:

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

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

Ve bu, dizinleyici delege olan sözlüğe iletildi Get(index):

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

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

Bu, dilin tasarlanma şeklidir.


Should outdeğişkenler cesaretini?

C #, onlara sahip olan ilk dil değildir ve belirli durumlarda amaçları vardır. Yüksek düzeyde eşzamanlı bir sistem oluşturmaya çalışıyorsanız out, eşzamanlılık sınırlarında değişkenleri kullanamazsınız .

Birçok yönden, dil ve çekirdek kütüphane sağlayıcıları tarafından benimsenen bir deyim varsa, API'larımdaki bu deyimleri benimsemeye çalışırım. Bu, API'yi bu dilde daha tutarlı ve evde hissettirir. Yani Ruby'de yazılmış bir yöntem C #, C veya Python'da yazılmış bir yöntem gibi görünmeyecektir. Her birinin kod oluşturmanın tercih edilen bir yolu vardır ve API'nızın kullanıcılarının bunu daha hızlı öğrenmesine yardımcı olacak şekilde çalışmaktır.


Haritalar Genel Olarak Bir Anti-Desen midir?

Amaçları vardır, ancak çoğu zaman sahip olduğunuz amaç için yanlış çözüm olabilirler. Özellikle çift yönlü bir eşlemeniz varsa ihtiyacınız vardır. Verileri düzenlemenin birçok kapsayıcı ve yolu vardır. Kullanabileceğiniz birçok yaklaşım vardır ve bazen bu kabı seçmeden önce biraz düşünmeniz gerekir.

Çok yönlü bir çift yönlü haritalama değerleri listeniz varsa, yalnızca bir grup listeye ihtiyacınız olabilir. Veya eşleştirmenin her iki tarafında ilk eşleşmeyi kolayca bulabileceğiniz bir yapı listesi.

Sorunlu alanı düşünün ve iş için en uygun aracı seçin. Bir tane yoksa, oluşturun.


4
"yalnızca tuples listesine ihtiyacınız olabilir. Veya eşleştirmenin her iki tarafında ilk eşleşmeyi kolayca bulabileceğiniz bir yapı listesi." - Küçük setler için optimizasyonlar varsa, bunlar kullanıcı koduna lastik damgalanmamış olarak kütüphanede yapılmalıdır.
Blrfl

Bir tuples listesi veya sözlük seçerseniz, bu bir uygulama ayrıntısıdır. Bu, sorunlu alanınızı anlamak ve iş için doğru aracı kullanmakla ilgilidir. Açıkçası, bir liste var ve bir sözlük var. Genel durum için sözlük doğrudur, ancak belki bir veya iki uygulama için listeyi kullanmanız gerekir.
Berin Loritsch

3
Ancak, eğer davranış hala aynı ise, bu tespit kütüphanede olmalıdır. Bir priori giriş sayısı küçük olacağını söyledi eğer algoritmaları değiştirecek diğer dillerde konteyner uygulamaları üzerinde çalıştım . Cevabınızı kabul ediyorum, ancak bu bir kez çözüldükten sonra, bir kütüphanede, belki bir SmallBidirectionalMap olarak olmalıdır.
Blrfl

5
"Daha iyi ya da daha kötü, C # kütüphane tasarımcıları davranış ile başa çıkmak için bir deyim geldi." - Sanırım kafasına çiviyi vurdun: isteğe bağlı bir dönüş tipi olan, yaygın olarak kullanılan mevcut olanı kullanmak yerine bir deyim ile "ortaya çıktılar".
Jörg W Mittag

3
@ JörgWMittag Eğer böyle bir şey hakkında konuşuyorsanız Option<T>, o zaman bu yöntemi C # 2.0'da kullanmak çok daha zor hale getirir, çünkü desen eşleşmesi yoktu.
svick

21

Burada hashtable / sözlüklerin genel ilkeleri hakkında bazı iyi cevaplar. Ama kod örneğine dokunacağımı sanıyordum,

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

C # 7 (yaklaşık iki yaşında olduğunu düşünüyorum) itibariyle, bu basitleştirilebilir:

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

Ve elbette bir satıra indirgenebilir:

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

Anahtarın olmadığı zaman için varsayılan bir değeriniz varsa, aşağıdakine dönüşebilir:

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

Böylece, makul düzeyde yeni dil eklemeleri kullanarak kompakt formlar elde edebilirsiniz.


1
V7 sözdizimi üzerinde iyi çağrı, var +1 kullanımına izin verirken ekstra tanım çizgisini kesmek zorunda kalmak için güzel bir küçük seçenek
BrianH

2
Eğer da unutmayın "key"mevcut değildir xüzere başlatılırdefault(TValue)
Peter Duniho

Genel uzantısı olarak güzel olabilir de benzeri çağrılan"key".DoSomethingWithThis()
Ross ayağı

getOrElse("key", defaultValue) Null nesnesini kullanan tasarımları tercih ederim . Bu şekilde çalışın ve TryGetValuedoğru veya yanlış döndürüp döndürmediğiniz umurunda değil .
candied_orange

1
Bu cevabın kompakt olması uğruna kompakt kod yazmanın kötü olduğunu feragat etmeyi hak ettiğini hissediyorum. Kodunuzu okumak çok daha zor olabilir. Ayrıca, doğru hatırlıyorsam, TryGetValueatomik / iş parçacığı için güvenli değildir , bu nedenle bir tane varlığı kontrol etmek ve diğeri değeri yakalamak ve değer üzerinde çalışmak için kolayca yapabilirsiniz
Marie

12

Bu bir kod kokusu veya anti-desen değildir, çünkü bir out parametresiyle TryGet tarzı işlevler kullanmak deyimsel C # 'dır. Ancak, bir Sözlük ile çalışmak için C # sağlanan 3 seçenek vardır, bu yüzden durumunuz için doğru olanı kullandığınızdan emin olmalısınız. Ben bir out parametresi kullanarak bir sorun olduğu söylentisi nereden geldiğini biliyorum, bu yüzden sonunda ele alacağım.

Bir C # Sözlüğü ile çalışırken kullanılacak özellikler:

  1. Anahtarın Sözlük'te olacağından eminseniz Item [TKey] özelliğini kullanın
  2. Anahtar genellikle sözlükte olmalı, ancak orada olmaması kötü / nadir / sorunluysa, Try ... Catch komutunu kullanmalısınız, böylece bir hata ortaya çıkar ve hatayı incelikle ele almayı deneyebilirsiniz
  3. Anahtarın Sözlük'te olacağından emin değilseniz, out parametresiyle TryGet kullanın

Bunu haklı çıkarmak için, sadece "Açıklamalar" altındaki Sözlük TryGetValue belgelerine başvurmanız gerekir :

Bu yöntem ContainsKey yönteminin ve Item [TKey] özelliğinin işlevselliğini birleştirir.

...

Kodunuz sık sık sözlükte olmayan anahtarlara erişmeye çalışırsa TryGetValue yöntemini kullanın. Bu yöntemi kullanmak Item [TKey] özelliği tarafından atılan KeyNotFoundException özelliğinin yakalanmasından daha etkilidir.

Bu yöntem bir O (1) işlemine yaklaşır.

TryGetValue'nun var olmasının tüm nedeni, ContainsKey ve Item [TKey] 'i kullanmak için daha uygun bir yol olarak hareket etmek, aynı zamanda sözlüğü iki kez aramak zorunda kalmamaktır - bu yüzden mevcut olmadığını ve iki şeyi elle yapmak oldukça gariptir. seçim.

Pratikte, bu basit maxim nedeniyle nadiren ham bir Sözlük kullandım: size ihtiyacınız olan işlevselliği veren en genel sınıfı / kapsayıcıyı seçin . Sözlük, anahtardan ziyade değere bakmak için tasarlanmamıştır (örneğin), bu istediğiniz bir şeyse, alternatif bir yapı kullanmak daha mantıklı olabilir. Geçen yıl yaptığım kalkınma projesinde Sözlük'ü bir kez kullanmış olabileceğimi düşünüyorum, çünkü yapmaya çalıştığım iş için nadiren doğru araçtı. Sözlük kesinlikle C # araç kutusunun İsviçre çakısı değil.

Out parametrelerinde yanlış olan ne?

CA1021: Parametreleri önleme

Dönüş değerleri yaygın ve yoğun olarak kullanılsa da, çıkış ve ref parametrelerinin doğru uygulanması ara tasarım ve kodlama becerileri gerektirir. Genel bir kitle için tasarım yapan kütüphane mimarları, kullanıcıların dışarıda çalışma veya ref parametreleriyle çalışmalarını beklememelidir.

Tahmin ediyorum ki parametreler bir anti-desen gibi bir şey duydunuz. Tüm kurallarda olduğu gibi, 'neden'i anlamak için daha yakından okumalısınız ve bu durumda Deneme deseninin kuralı nasıl ihlal etmediği konusunda açık bir söz bile vardır :

Try desenini uygulayan System.Int32.TryParse gibi yöntemler bu ihlali artırmaz.


Tüm rehberlik listesi, daha fazla insanın okumasını dilediğim bir şeydir. Takip edenler için işte bunun kökü. docs.microsoft.com/tr-tr/visualstudio/code-quality/…
Peter Wone

10

Bence diğer dillerde birçok durumda kodu önemli ölçüde temizlemek C # sözlükleri eksik en az iki yöntem vardır. Birincisi Option, Scala'da aşağıdaki gibi bir kod yazmanıza izin veren bir döndürme :

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

İkincisi, anahtar bulunmazsa kullanıcı tarafından belirtilen bir varsayılan değer döndürür:

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

Bir dilin uygun olduğunda, Trydesen gibi sağladığı deyimleri kullanmak için söylenecek bir şey vardır , ancak bu sadece dilin sağladığı şeyi kullanmanız gerektiği anlamına gelmez . Biz programcıyız. Özel durumumuzu daha kolay anlaşılır kılmak için, özellikle de tekrarları ortadan kaldırırsa, yeni soyutlamalar oluşturmakta fayda vardır. Sıklıkla geriye doğru arama veya değerler arasında yineleme gibi bir şeye ihtiyacınız varsa, bunu gerçekleştirin. İstediğiniz arayüzü oluşturun.


2. seçeneği seviyorum. Belki bir ya da iki uzatma yöntemi yazmalıyım :)
Adam B

2
artı tarafta c # uzantısı yöntemleri bunları kendiniz uygulayabilirsiniz anlamına gelir
jk.

5

Bu esasen aşağıdakileri yapmak için 4 kod satırıdır: DoSomethingWith(dict["key"])

Bunun yetersiz olduğuna katılıyorum. Değerin bir yapı türü olduğu bu durumda kullanmayı sevdiğim bir mekanizma:

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

Şimdi yeni bir sürümüne sahip TryGetValueolduğunu getiri int?. Daha sonra uzatmak için benzer bir numara yapabiliriz T?:

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

Ve şimdi bir araya getirin:

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

ve tek ve net bir ifadeye düştük.

Out anahtar kelime kullanarak bir anti-desen olduğunu duydum çünkü fonksiyonları parametrelerini mutasyon yapar.

Biraz daha az güçlü bir şekilde ifade edeceğim ve mümkün olduğunda mutasyonlardan kaçınmanın iyi bir fikir olduğunu söyleyebilirim.

Kendimi genellikle anahtarları ve değerleri çevirdiğim "tersine çevrilmiş" bir sözlüğe ihtiyaç duyuyorum.

Ardından çift yönlü bir sözlük uygulayın veya edinin. Yazmak kolaydır veya internette çok sayıda uygulama vardır. Burada bir dizi uygulama var, örneğin:

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

Benzer şekilde, genellikle bir sözlükteki öğeleri yinelemek ve kendimi daha iyi yapmak için anahtarları veya değerleri listelere vb.

Tabii, hepimiz yapıyoruz.

Sözlükleri kullanmanın neredeyse her zaman daha iyi, daha zarif bir yolu olduğunu hissediyorum, ama kaybım var.

Kendinize "varsayalım Dictionarytam olarak yapmak istediğim işlemi uygulayandan başka bir sınıfım olduğunu düşünün ; bu sınıf neye benzeyecekti?" Ardından, bu soruyu cevapladıktan sonra, o sınıfı uygulayın . Sen bir bilgisayar programcısısın. Bilgisayar programları yazarak sorunlarınızı çözün!


Teşekkürler. Eylem yönteminin gerçekte ne yaptığını biraz daha açıklayabileceğinizi düşünüyor musunuz? Ben eylemlerde yeniyim.
Adam B

1
@AdamB eylemi, bir geçersiz yöntem çağırma yeteneğini temsil eden bir nesnedir. Gördüğünüz gibi, arayan tarafta, parametre listesi eylemin genel tür argümanlarıyla eşleşen bir void yönteminin adını aktarıyoruz. Arayan tarafında eylem diğer herhangi bir yöntem gibi çağrılır.
Eric Lippert

1
@AdamB: Bu arayüze ne “uyguladığı” ve nasıl çağrılabileceği Action<T>konusunda çok benzer interface IAction<T> { void Invoke(T t); }, ama çok yumuşak kurallarla Invokedüşünebilirsiniz. Daha fazla bilgi edinmek isterseniz, C # 'da "delegeler" hakkında bilgi edinin ve sonra lambda ifadeleri hakkında bilgi edinin.
Eric Lippert

Tamam sanırım anladım. Böylece eylem, DoIt () parametresi olarak bir yöntem koyalım. Ve bu yöntemi çağırıyor.
Adam B

@AdamB: Kesinlikle doğru. Bu, "üst düzey işlevlerle işlevsel programlama" örneğidir. Çoğu işlev verileri bağımsız değişken olarak alır; üst düzey işlevler işlevleri bağımsız değişken olarak alır ve sonra bu işlevlerle bir şeyler yapar. Daha fazla C # öğrendikçe, LINQ'nun tamamen üst düzey işlevlerle uygulandığını göreceksiniz.
Eric Lippert

4

TryGetValue()Eğer "anahtar" sözlüğe ya da olmasın içinde bir anahtar olarak mevcut olup olmadığını bilmiyorsanız yapı aksi takdirde sadece gerekli olduğu DoSomethingWith(dict["key"])mükemmel geçerlidir.

"Daha az kirli" bir yaklaşım ContainsKey()bunun yerine çek olarak kullanılabilir.


6
"" Daha az kirli "bir yaklaşım, bunun yerine bir denetim olarak ContainsKey () kullanmak olabilir." Katılmıyorum. TryGetValueEn az olduğu gibi , en azından boş kasayı idare etmeyi unutmayı zorlaştırır. İdeal olarak, keşke bu sadece bir İsteğe bağlı dönecekti.
Alexander - Monica'yı eski

1
ContainsKeyKullanım süresi (TOCTOU) güvenlik açığı olan çok iş parçacıklı uygulamaların yaklaşımıyla ilgili potansiyel bir sorun vardır . Ne diğer bazı iplik için çağrılar arasında anahtarını silerse ContainsKeyve GetValue?
David Hammen

2
@JAD Opsiyonelleri oluşturur. BirOptional<Optional<T>>
Alexander - Reinstate Monica

2
@DavidHammen Açık olmak için ihtiyacınız TryGetValueolacak ConcurrentDictionary. Normal sözlük senkronize edilmez
Alexander - Reinstate Monica

2
@JAD Hayır, (Java'nın isteğe bağlı türü esiyor, beni başlatma). Daha soyut konuşuyorum
İskender - Reinstate Monica

2

Diğer cevaplar harika noktalar içeriyor, bu yüzden onları burada yeniden ifade etmeyeceğim, ancak bunun yerine şimdiye kadar büyük ölçüde göz ardı edilen bu bölüme odaklanacağım:

Benzer şekilde, genellikle bir sözlükteki öğeleri yinelemek ve kendimi daha iyi yapmak için anahtarları veya değerleri listelere vb.

Aslında, bir sözlük üzerinde yineleme yapmak oldukça kolaydır, çünkü aşağıdakileri uygular IEnumerable:

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

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

Linq'i tercih ederseniz, bu da iyi çalışır:

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

Sonuçta, sözlüklerin bir karşıtlık olduğunu düşünmüyorum - sadece belirli kullanımları olan belirli bir araç.

Ayrıca, daha da spesifikasyonlar için, SortedDictionary(daha tahmin edilebilir bir performans için RB ağaçlarını kullanır) ve SortedList(bu aynı zamanda arama hızı için ekleme hızından ödün veren, ancak sabit, önceden belirlenmiş bir sıralı küme). Bir yerine bir büyüklük daha hızlı yürütme sırası Dictionaryile SortedDictionarysonuçlanan bir dava yaşadım (ama başka bir şekilde de olabilir).


1

Bir sözlük kullanmanın garip olduğunu düşünüyorsanız, probleminiz için doğru seçim olmayabilir. Sözlükler harika, ancak bir yorumcu fark ettiği gibi, genellikle bir sınıf olması gereken bir şey için kısayol olarak kullanılırlar. Ya da sözlüğün kendisi bir çekirdek depolama yöntemi olarak doğru olabilir, ancak istenen hizmet yöntemlerini sağlamak için etrafında bir sarmalayıcı sınıf olmalıdır.

Dict [key] ve TryGet hakkında çok şey söylendi. Ne çok kullandığım KeyValuePair kullanarak sözlük üzerinde yineleme olduğunu. Görünüşe göre bu daha az bilinen bir yapıdır.

Bir sözlüğün ana nimetini, öğe sayısı arttıkça diğer koleksiyonlara kıyasla gerçekten hızlı olmasıdır. Bir anahtarın çok fazla olup olmadığını kontrol etmeniz gerekiyorsa, kendinize bir sözlük kullanımınızın uygun olup olmadığını sormak isteyebilirsiniz. Bir müşteri olarak, oraya ne koyduğunuzu ve böylece neyin sorgulanmasının güvenli olacağını bilmelisiniz.

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.