Zwracać wartość magiczną, zgłaszać wyjątek lub zwracać wartość false w przypadku niepowodzenia?

83

Czasami czasami muszę napisać metodę lub właściwość dla biblioteki klas, dla której nie jest niczym wyjątkowym brak prawdziwej odpowiedzi, ale niepowodzenie. Coś nie może zostać określone, nie jest dostępne, nie znaleziono, nie jest obecnie możliwe lub nie ma już dostępnych danych.

Myślę, że istnieją trzy możliwe rozwiązania dla tak stosunkowo nietypowej sytuacji, wskazujące na błąd w C # 4:

  • zwraca magiczną wartość, która nie ma żadnego znaczenia w inny sposób (np. nulli -1);
  • zgłosić wyjątek (np. KeyNotFoundException);
  • return falsei podaj rzeczywistą wartość zwracaną w outparametrze (np. Dictionary<,>.TryGetValue).

Tak więc pytania brzmią: w jakiej wyjątkowej sytuacji powinienem rzucić wyjątek? A jeśli nie powinienem rzucać: kiedy zwracana jest magiczna wartość przypisana powyżej zaimplementowania Try*metody z outparametrem ? (Dla mnie outparametr wydaje się brudny, a jego prawidłowe użycie wymaga więcej pracy.)

Szukam odpowiedzi faktycznych, takich jak odpowiedzi dotyczące wytycznych projektowych (nie znam żadnych Try*metod), użyteczności (ponieważ pytam o bibliotekę klas), zgodności z BCL i czytelności.


W bibliotece klas podstawowych .NET Framework stosowane są wszystkie trzy metody:

Zauważ, że jak Hashtablezostało utworzone w czasie, gdy nie było żadnych generycznych w C #, używa objecti dlatego może powrócić nulljako magiczna wartość. Ale w przypadku leków generycznych stosuje się wyjątki Dictionary<,>, które początkowo nie miały TryGetValue. Najwyraźniej wgląd się zmienia.

Oczywiście Item- TryGetValuei Parse- TryParsedwoistość jest tam bez powodu, więc zakładam, że rzucanie wyjątki dla non-wyjątkowych niepowodzeń jest w C # 4 nie zrobił . Jednak Try*metody nie zawsze istniały, nawet jeśli Dictionary<,>.Itemistniały.

Daniel AA Pelsmaeker
źródło
6
„wyjątki oznaczają błędy”. proszenie słownika o nieistniejący element jest błędem; proszenie strumienia o odczyt danych, gdy jest to EOF, za każdym razem, gdy korzystasz ze strumienia. (to podsumowuje długą pięknie sformatowaną odpowiedź, której nie miałem okazji przesłać :))
KutuluMike
6
Nie chodzi o to, że myślę, że twoje pytanie jest zbyt ogólne, ale o to, że nie jest to pytanie konstruktywne. Nie ma odpowiedzi kanonicznej, ponieważ odpowiedzi już pokazują.
George Stocker
2
@GeorgeStocker Gdyby odpowiedź była prosta i oczywista, nie zadałbym tego pytania. Fakt, że ludzie mogą się kłócić, dlaczego dany wybór jest lepszy z dowolnego punktu widzenia (takiego jak wydajność, czytelność, użyteczność, łatwość konserwacji, wytyczne projektowe lub spójność) sprawia, że ​​z natury niekanoniczny, ale odpowiedzialny za to z mojego zadowolenia. Odpowiedzi na wszystkie pytania są nieco subiektywne. Najwyraźniej oczekujesz, że pytanie będzie miało w większości podobne odpowiedzi, aby było dobrym pytaniem.
Daniel AA Pelsmaeker
3
@Virtlink: George jest moderatorem wybieranym przez społeczność, który poświęca wiele czasu, aby pomóc w utrzymaniu przepełnienia stosu. Stwierdził, dlaczego zamknął pytanie, a FAQ go popiera.
Eric J.
15
Pytanie należy tutaj nie dlatego, że jest subiektywne, ale dlatego, że jest konceptualne. Ogólna zasada: jeśli siedzisz przed kodowaniem IDE, zapytaj go o Przepełnienie stosu. Jeśli stoisz przed burzą mózgów na tablicy, zapytaj ją tutaj na Programistach.
Robert Harvey

Odpowiedzi:

56

Nie sądzę, że twoje przykłady są naprawdę równoważne. Istnieją trzy odrębne grupy, z których każda ma swoje uzasadnienie swojego zachowania.

  1. Wartość magiczna jest dobrą opcją, gdy występuje warunek „do”, taki jak StreamReader.Readlub gdy istnieje prosta w użyciu wartość, która nigdy nie będzie prawidłową odpowiedzią (-1 dla IndexOf).
  2. Zgłaszaj wyjątek, gdy semantyką funkcji jest to, że osoba wywołująca jest pewna, że ​​zadziała. W tym przypadku nieistniejący klucz lub zły podwójny format jest naprawdę wyjątkowy.
  3. Użyj parametru out i zwróć wartość bool, jeśli semantyka ma zbadać, czy operacja jest możliwa, czy nie.

Podane przykłady są całkowicie jasne dla przypadków 2 i 3. W przypadku wartości magicznych można argumentować, czy jest to dobra decyzja projektowa, czy nie we wszystkich przypadkach.

NaNZwrócony przez Math.Sqrtto szczególny przypadek - wynika pływający standardowego punktu.

Anders Abel
źródło
10
Nie zgadzaj się z numerem 1. Wartości magiczne nigdy nie są dobrą opcją, ponieważ wymagają następnego kodera, aby poznać znaczenie wartości magicznej. Uszkadzają również czytelność. Nie mogę wymyślić żadnego przypadku, w którym użycie magicznej wartości byłoby lepsze niż wzór Try.
0b101010
1
Ale co z Either<TLeft, TRight>monadą?
sara,
2
@ 0b101010: Właśnie spędziłem trochę czasu sprawdzając, jak streamreader.read może bezpiecznie zwrócić -1 ...
jmoreno
33

Próbujesz przekazać użytkownikowi interfejsu API, co musi zrobić. Jeśli rzucisz wyjątek, nic nie zmusi go do złapania go, a tylko czytanie dokumentacji da im znać, jakie są wszystkie możliwości. Osobiście uważam, że powolne i żmudne jest zagłębianie się w dokumentację, aby znaleźć wszystkie wyjątki, które może rzucić pewna metoda (nawet jeśli jest w inteligencji, wciąż muszę je ręcznie skopiować).

Magiczna wartość nadal wymaga przeczytania dokumentacji i ewentualnie odniesienia się constdo tabeli, aby zdekodować wartość. Przynajmniej nie ma narzutu wyjątku dla tego, co nazywasz zdarzeniem nietypowym.

Właśnie dlatego, mimo że outczasami nie są mile widziane parametry, wolę tę metodę ze Try...składnią. Jest to kanoniczna składnia .NET i C #. Użytkownik interfejsu API komunikuje, że musi sprawdzić wartość zwracaną przed użyciem wyniku. Możesz także dołączyć drugi outparametr z pomocnym komunikatem o błędzie, który ponownie pomaga w debugowaniu. Dlatego głosuję na rozwiązanie Try...z outparametrami.

Inną opcją jest zwrócenie specjalnego obiektu „wynikowego”, choć uważam to za o wiele bardziej nużące:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Wtedy twoja funkcja wygląda dobrze, ponieważ ma tylko parametry wejściowe i zwraca tylko jedną rzecz. Po prostu zwraca jedną krotkę.

W rzeczywistości, jeśli lubisz takie rzeczy, możesz użyć nowych Tuple<>klas, które zostały wprowadzone w .NET 4. Osobiście nie podoba mi się fakt, że znaczenie każdego pola jest mniej wyraźne, ponieważ nie mogę podać Item1i Item2przydatne nazwy.

Scott Whitlock
źródło
3
Osobiście często używam pojemnika wyników takiego jak twoja, IMyResultponieważ można przekazać bardziej złożony wynik niż tylko truelub falsewartość wyniku. Try*()jest użyteczny tylko w przypadku prostych rzeczy, takich jak konwersja ciągów znaków na int.
this.myself
1
Dobry post Dla mnie wolę idiom struktury wyników, który przedstawiłeś powyżej, zamiast podziału na wartości „return” i parametry „out”. Utrzymuje porządek.
Ocean Airdrop,
2
Problem z drugim parametrem wyjścia polega na tym, że gdy masz 50 funkcji głęboko w złożonym programie, w jaki sposób możesz przekazać użytkownikowi ten komunikat o błędzie? Zgłoszenie wyjątku jest znacznie prostsze niż stosowanie warstw sprawdzania błędów. Kiedy go dostaniesz, po prostu go rzuć i nie ma znaczenia, jak głęboko jesteś.
rzuca
@rolls - Kiedy używamy obiektów wyjściowych, przypuszczamy, że bezpośredni wywołujący zrobi wszystko, co zechce z danymi wyjściowymi: potraktuje to lokalnie, zignoruje lub wyrzuci bańki, rzucając to, co jest wyjątkiem. To najlepsze z obu słów - osoba dzwoniąca może wyraźnie zobaczyć wszystkie możliwe wyniki (z wyliczeniami itp.), Może zdecydować, co zrobić z błędem i nie musi próbować łapać za każdym razem. Jeśli przeważnie spodziewasz się natychmiast obsłużyć wynik lub go zignorować, zwracane obiekty są łatwiejsze. Jeśli chcesz wyrzucić wszystkie błędy na górne warstwy, wyjątki są łatwiejsze.
drizin
2
To po prostu wymyśla na nowo sprawdzone wyjątki w języku C # w znacznie bardziej nużący sposób niż sprawdzone wyjątki w Javie.
Zima
17

Jak pokazują twoje przykłady, każdy taki przypadek musi zostać oceniony osobno, a pomiędzy „wyjątkowymi okolicznościami” a „kontrolą przepływu” istnieje znaczne spektrum szarości, zwłaszcza jeśli twoja metoda ma być wielokrotnego użytku i może być stosowana w zupełnie innych wzorach niż pierwotnie został zaprojektowany. Nie oczekuj, że wszyscy tutaj uzgodnimy, co oznacza „nietypowy”, zwłaszcza jeśli od razu omówisz możliwość zastosowania „wyjątków” w celu wdrożenia tego.

Możemy również nie zgadzać się co do tego, który projekt sprawia, że ​​kod jest najłatwiejszy do odczytania i utrzymania, ale założę, że projektant biblioteki ma jasną osobistą wizję tego i musi jedynie zrównoważyć go z innymi rozważaniami.

Krótka odpowiedź

Podążaj za swoimi przeczuciami, chyba że projektujesz dość szybkie metody i oczekujesz możliwości nieprzewidzianego ponownego użycia.

Długa odpowiedź

Każdy przyszły dzwoniący może dowolnie tłumaczyć między kodami błędów i wyjątkami, jak chce w obu kierunkach; To sprawia, że ​​te dwa podejścia projektowe są prawie równoważne, z wyjątkiem wydajności, łatwości debugowania i niektórych ograniczonych kontekstów interoperacyjności. Zwykle sprowadza się to do wydajności, więc skupmy się na tym.

  • Zasadniczo spodziewaj się, że zgłoszenie wyjątku jest 200 razy wolniejsze niż zwykły zwrot (w rzeczywistości istnieje znaczna wariancja).

  • Jako kolejna reguła, zgłoszenie wyjątku może często pozwolić na znacznie czystszy kod w porównaniu z najokrutniejszymi magicznymi wartościami, ponieważ nie polegasz na tym, że programista tłumaczy kod błędu na inny kod błędu, ponieważ przechodzi on przez wiele warstw kodu klienta w kierunku punkt, w którym jest wystarczający kontekst do obsługi go w spójny i odpowiedni sposób. (Przypadek szczególny: nullma się tutaj lepiej niż inne wartości magiczne ze względu na jego tendencję do automatycznego przekładania się na NullReferenceExceptionniektóre, ale nie wszystkie rodzaje wad; zwykle, ale nie zawsze, całkiem blisko źródła defektu. )

Więc jaka jest lekcja?

W przypadku funkcji, która jest wywoływana tylko kilka razy w trakcie życia aplikacji (np. Inicjalizacja aplikacji), użyj czegokolwiek, co zapewnia czystszy, łatwiejszy do zrozumienia kod. Wydajność nie może być problemem.

Aby użyć funkcji „wyrzucania”, użyj czegokolwiek, co zapewni czystszy kod. Następnie wykonaj profilowanie (w razie potrzeby) i zmień wyjątki, aby zwracać kody, jeśli znajdują się wśród podejrzanych górnych wąskich gardeł na podstawie pomiarów lub ogólnej struktury programu.

W przypadku drogiej funkcji wielokrotnego użytku użyj wszystkiego, co zapewni czystszy kod. Jeśli w zasadzie zawsze trzeba przejść przez sieć w obie strony lub przeanalizować plik XML na dysku, narzut związany z rzuceniem wyjątku jest prawdopodobnie nieistotny. Ważniejsze jest, aby nie stracić szczegółów jakiejkolwiek awarii, nawet przypadkowo, niż wyjątkowo szybko powrócić z „awarii wyjątkowej”.

Lean funkcja wielokrotnego użytku wymaga więcej przemyślenia. Stosując wyjątki, wymuszasz coś w rodzaju 100-krotnego spowolnienia wywołujących, którzy zobaczą wyjątek w połowie (wielu) wywołań, jeśli ciało funkcji działa bardzo szybko. Wyjątki są nadal opcją projektową, ale będziesz musiał zapewnić alternatywę o niskim koszcie dla dzwoniących, którzy nie mogą sobie na to pozwolić. Spójrzmy na przykład.

Podajesz świetny przykład Dictionary<,>.Item, który, mówiąc luźno, zmienił się ze zwracania nullwartości na rzucanie KeyNotFoundExceptionmiędzy .NET 1.1 i .NET 2.0 (tylko jeśli jesteś gotów uważać Hashtable.Itemsię za jego praktycznego, nietypowego prekursora). Przyczyna tej „zmiany” nie jest tutaj bez zainteresowania. Optymalizacja wydajności typów wartości (koniec boksu) sprawiła, że ​​oryginalna magiczna wartość ( null) nie była opcją; outparametry po prostu przywróciłyby niewielką część kosztów wydajności. Ta ostatnia kwestia wydajności jest całkowicie nieistotna w porównaniu z narzutem związanym z rzucaniem a KeyNotFoundException, ale projekt wyjątku jest tutaj nadal lepszy. Dlaczego?

  • parametry ref / out ponoszą swoje koszty za każdym razem, nie tylko w przypadku „awarii”
  • Każdy, kogo to obchodzi, może zadzwonić Containsprzed każdym połączeniem z indeksem, a ten wzorzec czyta się całkowicie naturalnie. Jeśli programista chce, ale zapomina zadzwonić Contains, nie mogą wkraść się żadne problemy z wydajnością; KeyNotFoundExceptionjest wystarczająco głośny, aby zostać zauważonym i naprawiony.
Jirka Hanika
źródło
Myślę, że 200x jest optymistycznie nastawiony do doskonałości wyjątków ... patrz blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx sekcja „Wydajność i trendy” tuż przed komentarzami.
gbjbaanb
@gbjbaanb - Cóż, może. W tym artykule zastosowano wskaźnik niepowodzenia 1% do omówienia tematu, który nie jest zupełnie innym boiskiem. Moje własne przemyślenia i niejasno zapamiętane pomiary pochodzą z kontekstu C ++ zaimplementowanego w tabeli (zob. Sekcja 5.4.1.2 tego raportu , gdzie jednym problemem jest to, że pierwszy wyjątek tego rodzaju prawdopodobnie zacznie się od błędu strony (drastyczny i zmienny) ale raz zamrożona). Ale zrobię i opublikuję eksperyment z .NET 4 i prawdopodobnie poprawię tę wartość. Już podkreślam wariancję.
Jirka Hanika
Czy wtedy koszt parametrów ref / out jest tak wysoki ? Jak to? A dzwonienie Containsprzed wywołaniem do indeksatora może spowodować wyścig, w którym nie musi występować TryGetValue.
Daniel AA Pelsmaeker,
@gbjbaanb - eksperymentowano zakończone. Byłem leniwy i korzystałem z mono na Linuksie. Wyjątki dały mi ~ 563000 rzutów w 3 sekundy. Zwroty dały mi ~ 10900000 zwrotów w 3 sekundy. To 1:20, a nawet 1: 200. Nadal zalecam myślenie 1: 100+ dla bardziej realistycznego kodu. (nasz wariant param, jak przewidziałem, miał znikome koszty - podejrzewam, że jitter prawdopodobnie całkowicie zoptymalizował połączenie w moim minimalistycznym przykładzie, jeśli nie zgłoszono wyjątku, niezależnie od podpisu.)
Jirka Hanika
@Virtlink - ogólnie uzgodniono bezpieczeństwo wątków, ale biorąc pod uwagę, że w szczególności dotyczyłeś platformy .NET 4, użyj ConcurrentDictionarydla dostępu wielowątkowego i Dictionarydostępu jednowątkowego dla optymalnej wydajności. Oznacza to, że nieużywanie opcji `` Zawiera '' NIE czyni wątku kodu bezpiecznym dla tej konkretnej Dictionaryklasy.
Jirka Hanika,
10

Co najlepiej zrobić w tak stosunkowo nietypowej sytuacji, aby wskazać niepowodzenie i dlaczego?

Nie powinieneś dopuścić do niepowodzenia.

Wiem, że jest falujący i idealistyczny, ale wysłuchaj mnie. Podczas projektowania istnieje wiele przypadków faworyzowania wersji bez trybów awaryjnych. Zamiast „FindAll”, który zawodzi, LINQ używa klauzuli where, która po prostu zwraca pusty wyliczalny. Zamiast mieć obiekt, który należy zainicjować przed użyciem, pozwól konstruktorowi zainicjować obiekt (lub zainicjować, gdy wykryty zostanie niezainicjowany). Kluczem jest usunięcie gałęzi awarii w kodzie konsumenta. To jest problem, więc skup się na nim.

Inną strategią jest KeyNotFoundsscenario. W prawie każdej bazie kodu, nad którą pracowałem od 3.0, istnieje coś takiego jak ta metoda rozszerzenia:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Nie ma dla tego trybu realnej awarii. ConcurrentDictionaryma podobny GetOrAddwbudowany.

To powiedziawszy, zawsze będą chwile, w których będzie to po prostu nieuniknione. Wszystkie trzy mają swoje miejsce, ale wolałbym pierwszą opcję. Pomimo wszystkiego, co składa się na niebezpieczeństwo zerowe, jest dobrze znane i pasuje do wielu scenariuszy „nie znaleziono przedmiotu” lub „wynik nie ma zastosowania”, które składają się na zestaw „nie wyjątkowa porażka”. Zwłaszcza, gdy tworzysz typy wartości dopuszczających wartości zerowe, znaczenie „to może zawieść” jest bardzo wyraźne w kodzie i trudno je zapomnieć / zepsuć.

Druga opcja jest wystarczająca, gdy użytkownik robi coś głupiego. Daje ci ciąg znaków w niewłaściwym formacie, próbuje ustawić datę na 42 grudnia ... coś, co chcesz, aby wybuchło szybko i efektownie podczas testowania, aby zidentyfikować i naprawić zły kod.

Ostatnia opcja jest tą, której coraz bardziej nie lubię. Nasze parametry są niezręczne i zwykle naruszają niektóre z najlepszych praktyk podczas tworzenia metod, takich jak skupianie się na jednej rzeczy i brak efektów ubocznych. Dodatkowo, nasz parametr ma zwykle znaczenie tylko podczas sukcesu. To powiedziawszy, są one niezbędne dla niektórych operacji, które są zwykle ograniczone przez problemy związane ze współbieżnością lub względy wydajnościowe (gdzie na przykład nie chcesz robić drugiej podróży do bazy danych).

Jeśli zwracana wartość i parametr wyjściowy nie są trywialne, preferowana jest sugestia Scotta Whitlocka dotycząca obiektu wynikowego (jak Matchklasa Regex ).

Telastyn
źródło
7
Pet peeve tutaj: outparametry są całkowicie ortogonalne w stosunku do kwestii skutków ubocznych. Modyfikowanie refparametru jest efektem ubocznym, a modyfikowanie stanu obiektu, który przekazujesz za pomocą parametru wejściowego, jest efektem ubocznym, ale outparametr jest po prostu niewygodnym sposobem, aby funkcja zwracała więcej niż jedną wartość. Brak efektu ubocznego, tylko wielokrotne zwracane wartości.
Scott Whitlock,
Powiedziałem, że mają tendencję , ponieważ ludzie ich używają. Jak mówisz, są to po prostu wielokrotne zwracane wartości.
Telastyn
Ale jeśli nie lubisz parametrów i używasz wyjątków, aby spektakularnie wysadzić wszystko w niepoprawny format ... jak sobie poradzisz z przypadkiem, gdy twój format jest wprowadzany przez użytkownika? Wtedy albo użytkownik może wysadzić wszystko w powietrze, albo poniesie karę wydajności za rzucenie, a następnie złapanie wyjątku. Dobrze?
Daniel AA Pelsmaeker,
@virtlink poprzez odrębną metodę walidacji. Tak czy inaczej, musisz dostarczyć odpowiednie komunikaty do interfejsu użytkownika, zanim je prześlą.
Telastyn
1
Istnieje prawidłowy wzorzec dla parametrów wyjściowych i jest to funkcja, która ma przeciążenia zwracające różne typy. Rozdzielczość przeciążenia nie będzie działać dla typów zwracanych, ale będzie dla naszych parametrów.
Robert Harvey
2

Zawsze preferuj wyjątek. Ma jednolity interfejs wśród wszystkich funkcji, które mogą zawieść, i wskazuje awarię tak głośno, jak to możliwe - bardzo pożądana właściwość.

Zauważ, że Parsei TryParsetak naprawdę nie są tym samym, z wyjątkiem trybów awarii. Fakt, że TryParsemoże również zwracać wartość, jest nieco ortogonalny. Rozważ sytuację, w której na przykład sprawdzasz poprawność niektórych danych wejściowych. Tak naprawdę nie obchodzi cię, jaka jest wartość, o ile jest ona ważna. I nie ma nic złego w oferowaniu pewnego rodzaju IsValidFor(arguments)funkcji. Ale nigdy nie może być podstawowym trybem działania.

DeadMG
źródło
4
Jeśli masz do czynienia z dużą liczbą wywołań metody, wyjątki mogą mieć ogromny negatywny wpływ na wydajność. Wyjątki powinny być zarezerwowane dla wyjątkowych warunków i byłyby całkowicie akceptowalne dla sprawdzania poprawności danych wejściowych formularza, ale nie dla parsowania liczb z dużych plików.
Robert Harvey
1
To bardziej specjalistyczna potrzeba, a nie ogólny przypadek.
DeadMG,
2
Więc mówisz. Ale użyłeś słowa „zawsze”. :)
Robert Harvey
@DeadMG, zgodził się z RobertHarvey, chociaż myślę, że odpowiedź została zbyt mocno odrzucona, jeśli zmodyfikowano ją tak, by odzwierciedlała „większość czasu”, a następnie wskazano wyjątki (bez zamierzonej gry słów) w ogólnym przypadku, jeśli chodzi o często używaną wysoką wydajność połączenia, aby rozważyć inne opcje.
Gerald Davis,
Wyjątki nie są drogie. Łapanie głęboko rzuconych wyjątków może być kosztowne, ponieważ system musi rozwinąć stos do najbliższego punktu krytycznego. Ale ten „drogi” jest stosunkowo niewielki i nie należy się go obawiać nawet w ciasnych zagnieżdżonych pętlach.
Matthew Whited
2

Jak zauważyli inni, magiczna wartość (w tym logiczna wartość zwrotna) nie jest tak świetnym rozwiązaniem, z wyjątkiem znacznika „końca zakresu”. Powód: semantyka nie jest wyraźna, nawet jeśli zbadamy metody obiektu. Musisz przeczytać pełną dokumentację dla całego obiektu aż do „o tak, jeśli zwraca -42, co oznacza bla bla bla”.

Tego rozwiązania można użyć z przyczyn historycznych lub ze względu na wydajność, ale w innym przypadku należy go unikać.

Pozostawia to dwa ogólne przypadki: sondowanie lub wyjątki.

W tym przypadku ogólna zasada jest taka, że ​​program nie powinien reagować na wyjątki, z wyjątkiem sytuacji, gdy program postępuje, gdy program / przypadkowo / narusza jakiś warunek. Należy zastosować sondowanie, aby upewnić się, że tak się nie stanie. Dlatego wyjątek oznacza albo, że odpowiednie sondowanie nie zostało przeprowadzone z wyprzedzeniem, albo że wydarzyło się coś zupełnie nieoczekiwanego.

Przykład:

Chcesz utworzyć plik z podanej ścieżki.

Należy użyć obiektu File, aby z wyprzedzeniem ocenić, czy ta ścieżka jest zgodna z prawem do tworzenia lub zapisywania plików.

Jeśli twój program w jakiś sposób nadal próbuje pisać na ścieżce, która jest nielegalna lub w inny sposób niemożliwa do zapisu, powinieneś otrzymać ekscytację. Może się to zdarzyć z powodu wyścigu (inny użytkownik usunął katalog lub ustawił go jako tylko do odczytu, po wystąpieniu problemu)

Zadanie obsługi nieoczekiwanej awarii (sygnalizowanej przez wyjątek) i sprawdzenie, czy warunki są odpowiednie dla operacji z wyprzedzeniem (sondowanie), zwykle będą miały inną strukturę i dlatego powinny korzystać z różnych mechanizmów.

Anders Johansen
źródło
0

Myślę, że Trywzorzec jest najlepszym wyborem, gdy kod wskazuje tylko, co się stało. Nienawidzę param i lubię przedmiot zerowalny. Stworzyłem następującą klasę

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

więc mogę napisać następujący kod

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Wygląda na zerowy, ale nie tylko dla typu wartości

Inny przykład

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }
GSerjo
źródło
3
try catchsieją spustoszenie w twoim występie, jeśli dzwonisz GetXElement()i nie udaje ci się wiele razy.
Robert Harvey
czasami to nie ma znaczenia. Spójrz na wezwanie Bag. Dziękujemy za obserwację
twoja torba <T> jest prawie identyczna z System.Nullable <T> alias „obiekt zerowalny”
aeroson
tak, prawie public struct Nullable<T> where T : structgłówna różnica w ograniczeniu. btw najnowsza wersja jest tutaj github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/…
GSerjo
0

Jeśli interesuje Cię trasa „magicznej wartości”, jeszcze innym sposobem na rozwiązanie tego problemu jest przeciążenie celu klasy Lazy. Chociaż Lazy ma na celu odroczenie tworzenia instancji, nic tak naprawdę nie uniemożliwia korzystania z opcji Like lub Option. Na przykład:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
zumalifeguard
źródło