Często szukam pytań w Internecie, a wiele rozwiązań obejmuje słowniki. Jednak za każdym razem, gdy próbuję je zaimplementować, otrzymuję ten okropny smród w moim kodzie. Na przykład za każdym razem, gdy chcę użyć wartości:
int x;
if (dict.TryGetValue("key", out x)) {
DoSomethingWith(x);
}
To 4 linie kodu, które zasadniczo wykonują następujące czynności: DoSomethingWith(dict["key"])
Słyszałem, że użycie słowa kluczowego out jest anty-wzorcem, ponieważ powoduje, że funkcje mutują ich parametry.
Często też potrzebuję „odwróconego” słownika, w którym zmieniam klucze i wartości.
Podobnie często chciałbym iterować elementy w słowniku i przekonać się, że przekształcam klucze lub wartości na listy itp., Aby to zrobić lepiej.
Wydaje mi się, że prawie zawsze istnieje lepszy, bardziej elegancki sposób korzystania ze słowników, ale jestem zagubiony.
System.Collections.Generic
przestrzeni nazw nie są bezpieczne dla wątków .Dictionary
, tak naprawdę chcieli nowej klasy. Dla tych 50% (tylko) jest to zapach designerski.Odpowiedzi:
Słowniki (C # lub inne) to po prostu kontener, w którym wyszukujesz wartość na podstawie klucza. W wielu językach jest on bardziej poprawnie identyfikowany jako mapa, a najczęstszą implementacją jest HashMap.
Problemem do rozważenia jest to, co dzieje się, gdy klucz nie istnieje. Niektóre języki zachowują się, zwracając
null
lubnil
inną równoważną wartość. Cicha domyślna wartość zamiast informowania, że wartość nie istnieje.Na dobre lub na złe projektanci bibliotek C # wymyślili idiom, aby poradzić sobie z zachowaniem. Uznali, że domyślnym sposobem wyszukiwania wartości, która nie istnieje, jest zgłoszenie wyjątku. Jeśli chcesz uniknąć wyjątków, możesz użyć
Try
wariantu. Jest to to samo podejście, którego używają do parsowania łańcuchów na liczbach całkowitych lub obiektach daty / godziny. Zasadniczo wpływ jest następujący:I to przeszło do słownika, którego indeksator deleguje do
Get(index)
:Jest to po prostu sposób zaprojektowania języka.
Czy
out
należy odradzać zmienne?C # nie jest pierwszym językiem, który je ma i mają swój cel w określonych sytuacjach. Jeśli próbujesz zbudować wysoce współbieżny system, nie możesz używać
out
zmiennych na granicy współbieżności.Na wiele sposobów, jeśli istnieje idiom, który jest popierany przez język i głównych dostawców bibliotek, staram się przyjąć te idiomy w moich interfejsach API. Dzięki temu interfejs API wydaje się bardziej spójny w domu w tym języku. Zatem metoda napisana w Ruby nie będzie wyglądać jak metoda napisana w C #, C lub Python. Każdy z nich ma preferowany sposób budowania kodu, a praca z nim pomaga użytkownikom interfejsu API nauczyć się go szybciej.
Czy mapy są ogólnie anty-wzorem?
Mają swój cel, ale wiele razy mogą być złym rozwiązaniem dla twojego celu. Szczególnie, jeśli potrzebujesz mapowania dwukierunkowego, czego potrzebujesz. Istnieje wiele kontenerów i sposobów organizowania danych. Istnieje wiele metod, z których możesz skorzystać, a czasem musisz zastanowić się, zanim wybierzesz ten pojemnik.
Jeśli masz bardzo krótką listę dwukierunkowych wartości mapowania, może być potrzebna tylko lista krotek. Lub listę struktur, w której równie łatwo możesz znaleźć pierwsze dopasowanie po obu stronach mapowania.
Pomyśl o problematycznej dziedzinie i wybierz najbardziej odpowiednie narzędzie do pracy. Jeśli nie ma, utwórz go.
źródło
Option<T>
, myślę, że znacznie utrudniłoby to użycie tej metody w C # 2.0, ponieważ nie miała ona dopasowania wzorca.Kilka dobrych odpowiedzi tutaj na temat ogólnych zasad hashtables / słowników. Ale pomyślałem, że dotknę twojego przykładu kodu,
Począwszy od C # 7 (który moim zdaniem ma około dwóch lat), można to uprościć:
I oczywiście można go sprowadzić do jednej linii:
Jeśli masz wartość domyślną, gdy klucz nie istnieje, może on wyglądać następująco:
Możesz więc uzyskać kompaktowe formularze, używając stosunkowo nowych dodatków językowych.
źródło
"key"
nie jest obecny,x
zostanie zainicjowanydefault(TValue)
"key".DoSomethingWithThis()
getOrElse("key", defaultValue)
obiekt Null, to wciąż mój ulubiony wzór. Działaj w ten sposób, a nie obchodzi Cię, czyTryGetValue
zwróci wartość prawda czy fałsz.TryGetValue
nie jest bezpieczny dla atomów / wątków, więc równie łatwo można wykonać jedno sprawdzenie istnienia i drugie, aby chwycić i operować wartościąNie jest to ani zapach kodu, ani anty-wzór, ponieważ używanie funkcji w stylu TryGet z parametrem out to idiomatyczne C #. Istnieją jednak 3 opcje w języku C # do pracy ze słownikiem, więc jeśli masz pewność, że używasz odpowiedniego dla swojej sytuacji. Myślę, że wiem, skąd pochodzą pogłoski o problemie z użyciem parametru out, więc zajmę się tym na końcu.
Jakie funkcje używać podczas pracy ze słownikiem C #:
Aby to uzasadnić, wystarczy zajrzeć do dokumentacji Dictionary TryGetValue, w sekcji „Uwagi” :
Głównym powodem, dla którego istnieje TryGetValue, jest działanie jako wygodniejszy sposób korzystania z ContainsKey i Item [TKey], przy jednoczesnym unikaniu konieczności dwukrotnego przeszukiwania słownika - więc udawanie, że nie istnieje i wykonywanie tych dwóch czynności ręcznie jest raczej niezręczne wybór.
W praktyce rzadko korzystam z surowego słownika, z powodu tej prostej maksymy: wybierz najbardziej ogólną klasę / kontener, która zapewnia potrzebną funkcjonalność . Słownik nie został zaprojektowany tak, aby wyszukiwać według wartości, a nie klucza (na przykład), więc jeśli jest to coś, czego chcesz, sensowniejsze może być użycie alternatywnej struktury. Wydaje mi się, że kiedyś użyłem Słownika w ostatnim rocznym projekcie programistycznym, po prostu dlatego, że rzadko było to właściwe narzędzie do pracy, którą próbowałem wykonać. Słownik z pewnością nie jest szwajcarskim scyzorykiem z zestawu narzędzi C #.
Co jest nie tak z naszymi parametrami?
CA1021: Unikaj parametrów
Zgaduję, że właśnie tam usłyszałeś, że nasze parametry były czymś w rodzaju anty-wzorca. Podobnie jak w przypadku wszystkich reguł, powinieneś przeczytać bliżej, aby zrozumieć „dlaczego”, aw tym przypadku jest nawet wyraźna wzmianka o tym, jak wzorzec Try nie narusza reguły :
źródło
W słownikach C # brakuje co najmniej dwóch metod, które moim zdaniem znacznie oczyszczają kod w wielu sytuacjach w innych językach. Pierwszy zwraca an
Option
, który pozwala pisać kod w Scali w następujący sposób:Drugi zwraca wartość domyślną określoną przez użytkownika, jeśli klucz nie zostanie znaleziony:
Jest coś do powiedzenia na temat używania idiomów, które zapewnia język, gdy jest to właściwe, na przykład
Try
wzorca, ale to nie znaczy, że musisz używać tylko tego, co zapewnia język. Jesteśmy programistami. Można tworzyć nowe abstrakcje, aby ułatwić zrozumienie naszej konkretnej sytuacji, szczególnie jeśli eliminuje to wiele powtórzeń. Jeśli często potrzebujesz czegoś, na przykład wyszukiwania wstecznego lub iteracji po wartościach, zrób to. Utwórz interfejs, który chcesz mieć.źródło
Zgadzam się, że to nieeleganckie. Mechanizm, który lubię używać w tym przypadku, w którym wartość jest typem struktury, to:
Teraz mamy nową wersję
TryGetValue
tego powrotuint?
. Następnie możemy wykonać podobną sztuczkę w celu przedłużeniaT?
:A teraz złóż to razem:
i sprowadzamy się do jednego, jasnego stwierdzenia.
Byłbym nieco słabiej sformułowany i powiedziałbym, że unikanie mutacji, gdy jest to możliwe, jest dobrym pomysłem.
Następnie zaimplementuj lub uzyskaj słownik dwukierunkowy. Można je łatwo napisać lub w Internecie dostępnych jest wiele wdrożeń. Istnieje tutaj wiele implementacji, na przykład:
/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp
Jasne, że wszyscy.
Zadaj sobie pytanie „przypuśćmy, że miałem klasę inną niż ta,
Dictionary
która zaimplementowała dokładnie tę operację, którą chciałbym wykonać; jak wyglądałaby ta klasa?” Następnie, po udzieleniu odpowiedzi na to pytanie, zaimplementuj tę klasę . Jesteś programistą komputerowym. Rozwiąż swoje problemy pisząc programy komputerowe!źródło
Action<T>
jest bardzo podobnyinterface IAction<T> { void Invoke(T t); }
, ale z bardzo łagodnymi regułami dotyczącymi tego, co „implementuje” ten interfejs i jakInvoke
można go wywoływać. Jeśli chcesz dowiedzieć się więcej, dowiedz się o „delegatach” w C #, a następnie dowiedz się o wyrażeniach lambda.TryGetValue()
Konstrukt jest konieczne tylko wtedy, jeśli nie wiem, czy „klucz” jest obecny jako klucz w słowniku, czy nie, w przeciwnym razieDoSomethingWith(dict["key"])
jest całkowicie poprawny.ContainsKey()
Zamiast tego „mniej brudnym” podejściem może być sprawdzenie.źródło
TryGetValue
, przynajmniej utrudnia zapomnienie o obsłudze pustej skrzynki. Idealnie chciałbym, aby to po prostu zwróciło Opcjonalne.ContainsKey
podejściem do aplikacji wielowątkowych, czyli luki w zabezpieczeniach od czasu sprawdzenia do momentu użycia (TOCTOU). Co się stanie, jeśli jakiś inny wątek usunie klucz między połączeniami doContainsKey
iGetValue
?Optional<Optional<T>>
TryGetValue
naConcurrentDictionary
. Zwykły słownik nie został zsynchronizowanyInne odpowiedzi zawierają świetne punkty, więc nie będę ich tutaj powtarzał, ale skupię się na tej części, która do tej pory wydaje się w dużej mierze ignorowana:
W rzeczywistości iteracja słownika jest dość prosta, ponieważ implementuje on
IEnumerable
:Jeśli wolisz Linq, to też działa dobrze:
Podsumowując, nie uważam Słowników za antypattern - są one tylko konkretnym narzędziem o określonych zastosowaniach.
Ponadto, aby uzyskać jeszcze więcej szczegółów, sprawdź
SortedDictionary
(używa drzew RB dla bardziej przewidywalnej wydajności) iSortedList
(który jest także myląco nazwanym słownikiem, który poświęca szybkość wstawiania dla prędkości wyszukiwania, ale świeci, jeśli gapisz się na ustalone, wstępne posortowany zestaw). Miałem przypadek, w którym wymieniaszDictionary
zSortedDictionary
zaowocowało rząd wielkości szybciej wykonanie (ale może się zdarzyć w drugą stronę też).źródło
Jeśli uważasz, że używanie słownika jest niewygodne, może nie być dobrym wyborem dla twojego problemu. Słowniki są świetne, ale jak zauważył jeden z komentatorów, często są używane jako skrót do czegoś, co powinno być klasą. Albo sam słownik może być właściwy jako podstawowa metoda przechowywania, ale powinna być wokół niego klasa opakowania, aby zapewnić pożądane metody obsługi.
Wiele powiedziano o Dict [klucz] kontra TryGet. Często używam iteracji w słowniku za pomocą KeyValuePair. Najwyraźniej jest to mniej znany konstrukt.
Główną zaletą słownika jest to, że jest on naprawdę szybki w porównaniu do innych kolekcji wraz ze wzrostem liczby przedmiotów. Jeśli musisz sprawdzić, czy klucz istnieje, możesz zadać sobie pytanie, czy użycie słownika jest właściwe. Jako klient zazwyczaj powinieneś wiedzieć, co tam umieścisz, a tym samym, co można bezpiecznie zapytać.
źródło