Najlepszy wyjątek dla nieprawidłowego argumentu typu ogólnego

106

Obecnie piszę kod dla UnconstrainedMelody, który ma ogólne metody do wyliczenia.

Teraz mam statycznych klasy z grupą metod, które są wyłącznie przeznaczone do pracy z „flagi” teksty stałe. Nie mogę dodać tego jako ograniczenia ... więc możliwe, że będą wywoływane także z innymi typami wyliczeń. W takim przypadku chciałbym zgłosić wyjątek, ale nie jestem pewien, który z nich rzucić.

Żeby zrobić ten konkret, jeśli mam coś takiego:

// Returns a value with all bits set by any values
public static T GetBitMask<T>() where T : struct, IEnumConstraint
{
    if (!IsFlags<T>()) // This method doesn't throw
    {
        throw new ???
    }
    // Normal work here
}

Jaki jest najlepszy wyjątek do rzucenia? ArgumentExceptionbrzmi logicznie, ale jest to raczej argument typu niż normalny argument, który może łatwo zmylić sprawę . Czy powinienem przedstawić własną TypeArgumentExceptionklasę? Użyj InvalidOperationException? NotSupportedException? Coś jeszcze?

Bym raczej nie tworzyć własne wyjątek dla tego chyba że jest to wyraźnie, co trzeba zrobić.

Jon Skeet
źródło
Natknąłem się na to dzisiaj, pisząc metodę ogólną, w której nakładane są dodatkowe wymagania dotyczące używanego typu, których nie można opisać za pomocą ograniczeń. Zaskoczyło mnie, że nie znalazłem typu wyjątku już w BCL. Ale właśnie z tym dylematem stanąłem kilka dni temu w tym samym projekcie dotyczącym produktu generycznego, który będzie działał tylko z atrybutem Flagi. Straszny!
Andras Zoltan

Odpowiedzi:

46

NotSupportedException wygląda na to, że po prostu pasuje, ale dokumentacja wyraźnie stwierdza, że ​​powinien być używany w innym celu. Z uwag klasy MSDN:

Istnieją metody, które nie są obsługiwane w klasie bazowej, z oczekiwaniem, że zamiast tego metody te zostaną zaimplementowane w klasach pochodnych. Klasa pochodna może implementować tylko podzbiór metod z klasy bazowej i zgłosić NotSupportedException dla nieobsługiwanych metod.

Oczywiście istnieje sposób, który NotSupportedExceptionjest oczywiście wystarczająco dobry, zwłaszcza biorąc pod uwagę jego zdroworozsądkowe znaczenie. Powiedziawszy to, nie jestem pewien, czy to w porządku.

Biorąc pod uwagę cel Unconstrained Melody ...

Istnieje wiele przydatnych rzeczy, które można wykonać za pomocą ogólnych metod / klas, w których istnieje ograniczenie typu „T: enum” lub „T: delegate” - ale niestety są one zabronione w C #.

Ta biblioteka narzędzi omija zakazy używania ildasm / ilasm ...

... wydaje się, Exceptionże nowy może być w porządku, pomimo dużego ciężaru dowodu, który słusznie musimy spełnić przed stworzeniem niestandardowego Exceptions. Coś takiego InvalidTypeParameterExceptionmoże być przydatne w całej bibliotece (a może nie - to z pewnością przypadek skrajny, prawda?).

Czy klienci będą musieli umieć odróżnić to od wyjątków BCL? Kiedy klient może przypadkowo nazwać to, używając wanilii enum? Jak odpowiedziałbyś na pytania postawione przez zaakceptowaną odpowiedź na jakie czynniki należy wziąć pod uwagę podczas pisania niestandardowej klasy wyjątków?

Jeff Sternal
źródło
W rzeczywistości prawie kuszące jest rzucenie wyjątku tylko wewnętrznego w pierwszej kolejności, w ten sam sposób, w jaki robi to Code Contracts ... Nie sądzę, aby ktokolwiek powinien to łapać.
Jon Skeet,
Szkoda, że ​​nie może po prostu zwrócić wartości null!
Jeff Sternal,
25
Idę z TypeArgumentException.
Jon Skeet,
Dodawanie wyjątków do Framework może wiązać się z dużym „ciężarem dowodu”, ale definiowanie niestandardowych wyjątków nie powinno. InvalidOperationExceptionTakie rzeczy są okropne, ponieważ „Foo prosi Collection Bar o dodanie czegoś, co już istnieje, więc Bar rzuca IOE” i „Foo prosi Collection Bar o dodanie czegoś, więc Bar wywołuje Boz, który wyrzuca IOE, mimo że Bar tego nie oczekuje” oba wyrzucą ten sam typ wyjątku; kod, który spodziewa się złapać pierwszy, nie będzie oczekiwał drugiego. Powiedziawszy to ...
superkat
... Myślę, że argument przemawiający za wyjątkiem ramowym jest tutaj bardziej przekonujący niż w przypadku niestandardowego wyjątku. Ogólną naturą NSE jest to, że gdy odniesienie do obiektu jako typu ogólnego i niektórych, ale nie wszystkich, konkretnych typów obiektów, do których punkty odniesienia będą wspierać zdolność, próbuje użyć zdolności na określonym typie, co nie nie popieram, powinno rzucać NSE. Uznałbym a Foo<T>za „typ ogólny” i Foo<Bar>za „typ szczególny” w tym kontekście, mimo że nie ma między nimi relacji „dziedziczenia”.
supercat
24

Unikałbym NotSupportedException. Ten wyjątek jest używany w ramach, w którym metoda nie jest zaimplementowana i istnieje właściwość wskazująca, że ​​ten typ operacji nie jest obsługiwany. Nie pasuje tutaj

Myślę, że InvalidOperationException to najbardziej odpowiedni wyjątek, jaki można tutaj rzucić.

JaredPar
źródło
Dzięki za ostrzeżenie o NSE. Z zadowoleniem przyjmuję też wkład twoich kolegów, przy okazji ...
Jon Skeet,
Chodzi o to, że funkcjonalność, której Jon potrzebuje, nie ma nic podobnego w BCL. Kompilator powinien to złapać. Jeśli usuniesz wymaganie „property” z NotSupportedException, wspomniane elementy (takie jak kolekcja ReadOnly) są najbliższe problemowi Jona.
Mehrdad Afshari
Jedna uwaga - mam metodę IsFlags (musi to być metoda ogólna), która w pewnym sensie wskazuje, że ten typ operacji nie jest obsługiwany ... więc w tym sensie NSE byłoby odpowiednie. tzn. dzwoniący może najpierw sprawdzić.
Jon Skeet,
@Jon: Myślę, że nawet jeśli nie masz takiej właściwości, ale wszyscy członkowie twojego typu z natury polegają na fakcie, że Tjest enumozdobiony Flags, byłoby ważne, aby rzucić NSE.
Mehrdad Afshari
1
@Jon: StupidClrExceptionto zabawne imię;)
Mehrdad Afshari
13

Programowanie ogólne nie powinno generować w czasie wykonywania nieprawidłowych parametrów typu. Nie powinien się kompilować, powinieneś mieć wymuszenie czasu kompilacji. Nie wiem, co IsFlag<T>()zawiera, ale być może możesz to zmienić w wymuszanie czasu kompilacji, na przykład próbując stworzyć typ, który można utworzyć tylko za pomocą „flag”. Może traitsklasa może pomóc.

Aktualizacja

Jeśli musisz rzucić, zagłosowałbym na InvalidOperationException. Rozumowanie jest takie, że typy ogólne mają parametry, a błędy związane z parametrami (metody) są wyśrodkowane wokół hierarchii ArgumentException. Jednak zalecenie dotyczące ArgumentException stwierdza, że

jeśli awaria nie dotyczy samych argumentów, należy użyć InvalidOperationException.

Jest co najmniej jeden skok wiary w to, że zalecenia dotyczące parametrów metod mają być również stosowane do parametrów ogólnych , ale nie ma nic lepszego w SystemException hierachy imho.

Remus Rusanu
źródło
1
Nie, nie ma możliwości ograniczenia tego w czasie kompilacji. IsFlag<T>określa, czy wyliczenie zostało [FlagsAttribute]zastosowane do niego, a środowisko CLR nie ma ograniczeń opartych na atrybutach. Byłoby to w idealnym świecie - albo byłby inny sposób, aby to ograniczyć - ale w tym przypadku to po prostu nie działa :(
Jon Skeet,
(+1 za ogólną zasadę - chciałbym móc to ograniczyć.)
Jon Skeet
9

Użyłbym NotSupportedException, ponieważ to właśnie mówisz. Inne wyliczenia niż określone nie są obsługiwane . Oczywiście byłoby to wyraźniej określone w komunikacie o wyjątku.

Robban
źródło
2
NotSupportedException jest używany w bardzo różnych celach w BCL. Nie pasuje tutaj. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

Poszedłbym z NotSupportedException. Choć ArgumentExceptionwygląda dobrze, to naprawdę spodziewać po argument przekazywany do metody jest nie do przyjęcia. Argument typu jest cechą definiującą rzeczywistą metodę, którą chcesz wywołać, a nie prawdziwym „argumentem”. InvalidOperationExceptionpowinno być wyrzucane, gdy wykonywana operacja może być prawidłowa w niektórych przypadkach, ale w konkretnej sytuacji jest to niedopuszczalne.

NotSupportedExceptionjest generowany, gdy operacja jest z natury nieobsługiwana. Na przykład podczas implementowania interfejsu, w którym określony element członkowski nie ma sensu dla klasy. To wygląda na podobną sytuację.

Mehrdad Afshari
źródło
Mmm. Jeszcze nie całkiem czuję się dobrze, ale myślę, że będzie to najbliższa rzecz do niego.
Jon Skeet,
Jon: Nie wydaje się to właściwe, ponieważ naturalnie oczekujemy, że zostanie przechwycony przez kompilator.
Mehrdad Afshari
Tak. To dziwny rodzaj ograniczenia, które chciałbym zastosować, ale nie mogę :)
Jon Skeet
6

Najwyraźniej firma Microsoft używa ArgumentExceptiondo tego celu, jak pokazano na przykładzie Expression.Lambda <> , Enum.TryParse <> lub Marshal.GetDelegateForFunctionPointer <> w sekcji Wyjątki. Nie mogłem znaleźć żadnego przykładu wskazującego inaczej (pomimo wyszukiwania lokalnego źródła odniesienia dla TDelegatei TEnum).

Myślę więc, że można bezpiecznie założyć, że przynajmniej w kodzie firmy Microsoft powszechną praktyką jest stosowanie ArgumentExceptionnieprawidłowych argumentów typu ogólnego oprócz podstawowych zmiennych. Biorąc pod uwagę, że opis wyjątków w dokumentach nie rozróżnia tych wyjątków , nie jest to również zbyt trudne.

Miejmy nadzieję, że raz na zawsze zadecyduje o wszystkim.

Alice
źródło
Pojedynczy przykład we frameworku nie jest dla mnie wystarczający, nie - biorąc pod uwagę liczbę miejsc, w których moim zdaniem SM dokonało złego wyboru w innych przypadkach :) Nie wywodziłbym się TypeArgumentExceptionz tego ArgumentException, po prostu dlatego, że argument typu nie jest regularny argument.
Jon Skeet
1
Jest to z pewnością bardziej przekonujące pod względem „tego, co konsekwentnie robi SM”. Nie czyni go bardziej przekonującym pod względem dopasowania dokumentacji ... i wiem, że w zespole C # jest wiele osób, którym bardzo zależy na różnicy między zwykłymi argumentami a argumentami typu :) Ale dzięki za przykłady - są bardzo pomocne.
Jon Skeet
@Jon Skeet: Dokonano edycji; teraz zawiera 3 przykłady z różnych bibliotek MS, wszystkie z ArgumentException udokumentowanym jako wrzucony; więc jeśli jest to zły wybór, przynajmniej jest to konsekwentny zły wybór. ;) Myślę, że Microsoft zakłada, że ​​zarówno zwykłe argumenty, jak i argumenty typu są argumentami; i osobiście uważam, że takie założenie jest całkiem rozsądne. ^^ '
Alice,
Ach, nieważne, wydaje się, że już to zauważyłeś. Cieszę się, że mogłem pomóc. ^^
Alice,
Myślę, że będziemy musieli zgodzić się na nieporozumienie co do tego, czy rozsądne jest traktowanie ich tak samo. Z pewnością nie są takie same, jeśli chodzi o refleksję, zasady językowe itp. ... są traktowane zupełnie inaczej.
Jon Skeet
3

Idę z NotSupportedExpcetion.

Carl Bergquist
źródło
2

Zgłaszanie niestandardowego wyjątku powinno być zawsze wykonywane w każdym przypadku, gdy jest to wątpliwe. Wyjątek niestandardowy zawsze będzie działał, niezależnie od potrzeb użytkowników interfejsu API. Deweloper może złapać dowolny typ wyjątku, jeśli go to nie obchodzi, ale jeśli programista wymaga specjalnej obsługi, będzie SOL.

Eric Schneider
źródło
Deweloper powinien również udokumentować wszystkie wyjątki zgłoszone w komentarzach XML.
Eric Schneider
1

Co powiesz na dziedziczenie po NotSupportedException. Chociaż zgadzam się z @Mehrdad, że ma to największy sens, słyszę twój punkt widzenia, że ​​nie wydaje się pasować idealnie. Więc dziedzicz po NotSupportedException, dzięki czemu ludzie kodujący w Twoim interfejsie API mogą nadal przechwytywać NotSupportedException.

BFree
źródło
1

Zawsze uważam na pisanie niestandardowych wyjątków, wyłącznie z tego powodu, że nie zawsze są one jasno udokumentowane i powodują zamieszanie, jeśli nie zostaną poprawnie nazwane.

W tym przypadku zgłosiłbym ArgumentException dla niepowodzenia sprawdzania flag. Tak naprawdę wszystko zależy od preferencji. Niektóre standardy kodowania, które widziałem, posunęły się nawet do określenia, które typy wyjątków powinny być stosowane w takich scenariuszach.

Jeśli użytkownik próbował przekazać coś, co nie było wyliczeniem, zgłosiłbym InvalidOperationException.

Edytować:

Inni podnoszą interesującą kwestię, że nie jest to obsługiwane. Moim jedynym zmartwieniem związanym z NotSupportedException jest to, że generalnie są to wyjątki, które są wyrzucane po wprowadzeniu do systemu „ciemnej materii” lub, mówiąc inaczej, „Ta metoda musi zostać wprowadzona do systemu w tym interfejsie, ale wygraliśmy nie włączaj go do wersji 2.4 "

Widziałem również NotSupportedExceptions jako wyjątek licencyjny „korzystasz z darmowej wersji tego oprogramowania, ta funkcja nie jest obsługiwana”.

Edycja 2:

Inny możliwy:

System.ComponentModel.InvalidEnumArgumentException  

Wyjątek zgłoszony podczas używania nieprawidłowych argumentów, które są modułami wyliczającymi.

Piotr
źródło
Będę ograniczał się do wyliczenia (po kilku jiggery pokery) - martwię się tylko o flagi.
Jon Skeet,
Myślę, że licencjodawcy powinni rzucić przykład LicensingExceptionklasy dziedziczącej po InvalidOperationException.
Mehrdad Afshari,
Zgadzam się, Mehrdad, Wyjątki są niestety jednym z tych obszarów, w których jest dużo szarości w ramach. Ale jestem pewien, że to samo dotyczy wielu języków. (nie mówiąc, że wróciłbym do błędu czasu wykonania vb6 13 hehe)
Peter