Widząc, że C # nie może być switch
na typie (który, jak uznaję, nie został dodany jako szczególny przypadek, ponieważ is
relacje oznaczają, że case
może mieć zastosowanie więcej niż jedna odmiana), czy jest lepszy sposób na symulację włączenia typu innego niż ten?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
switch
instrukcję. Jeden przykład: zestaw A zawiera zestaw obiektów danych (które nie zostaną zmienione, zdefiniowane w dokumencie specyfikacji lub tym podobne). Zespoły B , C i D każde odniesienie i dostarczenie konwersji dla różnych obiektów danych z A (np serializacji / deserializacji pewnym określonym formacie). Musisz albo odzwierciedlić całą hierarchię klas w B , C i D i korzystać z fabryk, albo masz ...Odpowiedzi:
W C # zdecydowanie brakuje przełączania typów ( AKTUALIZACJA: w C # 7 / VS 2017 włączanie typów jest obsługiwane - patrz odpowiedź Zachary'ego Yatesa poniżej ). Aby to zrobić bez dużej instrukcji if / else if / else, musisz pracować z inną strukturą. Niedługo napisałem post na blogu, w którym szczegółowo opisałem, jak zbudować strukturę TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Wersja skrócona: TypeSwitch ma za zadanie zapobiegać redundantnym rzutowaniom i zapewniać składnię podobną do normalnej instrukcji switch / case. Na przykład tutaj jest TypeSwitch w akcji na standardowym zdarzeniu formularza Windows
Kod TypeSwitch jest w rzeczywistości dość mały i można go łatwo umieścić w projekcie.
źródło
foreach
(co by się kiedykolwiek zdarzyło, gdyby nie znaleziono dopasowania)CaseInfo
prostu sprawdzając wartość typu (jeśli jej wartość jest zerowa, to domyślna).W wersji C # 7 dostarczonej z programem Visual Studio 2017 (wydanie 15. *) możesz używać typów w
case
instrukcjach (dopasowywanie wzorca):W C # 6 możesz użyć instrukcji switch z operatorem nameof () (dzięki @Joey Adams):
W wersji C # 5 i wcześniejszych możesz użyć instrukcji switch, ale będziesz musiał użyć magicznego ciągu zawierającego nazwę typu ... który nie jest szczególnie przyjazny dla refaktorów (dzięki @nukefusion)
źródło
nameof()
operatora .case UnauthorizedException _:
Jedną z opcji jest posiadanie słownika od
Type
doAction
(lub innego delegata). Wyszukaj akcję na podstawie typu, a następnie wykonaj ją. Użyłem tego wcześniej do fabryk.źródło
Z odpowiedzią JaredPara z tyłu głowy napisałem wariant jego
TypeSwitch
klasy, który używa wnioskowania typu dla lepszej składni:Pamiętaj, że kolejność
Case()
metod jest ważna.Uzyskaj pełny i skomentowany kod dla mojej
TypeSwitch
klasy . To jest działająca wersja skrócona:źródło
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. To pozwala powiedziećvalue.Case((C x) ...
Utwórz nadklasę (S) i odziedzicz po niej A i B. Następnie zadeklaruj abstrakcyjną metodę na S, którą każda podklasa musi zaimplementować.
Robiąc to, metoda „foo” może również zmienić swój podpis na Foo (S o), dzięki czemu jest bezpieczny i nie musisz rzucać tego brzydkiego wyjątku.
źródło
Możesz użyć dopasowania wzorca w C # 7 lub nowszej:
źródło
Naprawdę powinieneś przeciążać swoją metodę, nie próbując samemu ujednoznacznić. Większość dotychczasowych odpowiedzi nie uwzględnia przyszłych podklas, co może później prowadzić do naprawdę strasznych problemów z obsługą.
źródło
Jeśli korzystasz z C # 4, możesz skorzystać z nowej dynamicznej funkcjonalności, aby uzyskać interesującą alternatywę. Nie twierdzę, że tak jest lepiej, w rzeczywistości wydaje się bardzo prawdopodobne, że będzie wolniejszy, ale ma w sobie pewną elegancję.
A użycie:
Powodem tego jest fakt, że wywołanie metody dynamicznej w języku C # 4 zostało rozwiązane w czasie wykonywania zamiast w czasie kompilacji. Niedawno napisałem nieco więcej o tym pomyśle . Ponownie chciałbym tylko powtórzyć, że to prawdopodobnie działa gorzej niż wszystkie inne sugestie, oferuję to po prostu jako ciekawość.
źródło
Tak, dzięki C # 7, który można osiągnąć. Oto jak to się robi (używając wzorca wyrażenia ):
źródło
W przypadku typów wbudowanych można użyć wyliczenia TypeCode. Należy pamiętać, że GetType () jest dość powolny, ale prawdopodobnie nie jest istotny w większości sytuacji.
Dla niestandardowych typów możesz utworzyć własne wyliczenie oraz interfejs lub klasę podstawową z abstrakcyjną właściwością lub metodą ...
Implementacja własności klasy abstrakcyjnej
Implementacja metody klasy abstrakcyjnej
Implementacja interfejsu własności
Implementacja metody interfejsu
Jeden z moich współpracowników właśnie mi o tym powiedział: ma tę zaletę, że można go używać do dosłownie każdego rodzaju obiektu, nie tylko tych, które zdefiniujesz. Ma tę wadę, że jest nieco większy i wolniejszy.
Najpierw zdefiniuj klasę statyczną w następujący sposób:
A potem możesz użyć tego w następujący sposób:
źródło
Podobało mi się użycie przez Virtlink pisania niejawnego, aby uczynić przełącznik znacznie bardziej czytelnym, ale nie podobało mi się, że wczesne wyjście nie jest możliwe i że dokonujemy alokacji. Podkręćmy trochę perf.
Cóż, to mnie boli. Zróbmy to w T4:
Trochę zmieniając przykład Virtlink:
Czytelny i szybki. Teraz, jak wszyscy wskazują w swoich odpowiedziach i biorąc pod uwagę naturę tego pytania, kolejność jest ważna w dopasowywaniu typów. W związku z tym:
źródło
Biorąc pod uwagę, że dziedziczenie ułatwia rozpoznanie obiektu jako więcej niż jednego typu, myślę, że zmiana może prowadzić do złej dwuznaczności. Na przykład:
Przypadek 1
Przypadek 2
Ponieważ s jest ciągiem i obiektem. Myślę, że kiedy piszesz
switch(foo)
, oczekujesz, że foo będzie pasowało do jednego i tylko jednegocase
stwierdzeń. Po włączeniu typów kolejność pisania instrukcji case może zmienić wynik całej instrukcji switch. Myślę, że to byłoby złe.Można pomyśleć o sprawdzeniu kompilatora typów instrukcji „typwitch”, sprawdzeniu, czy wyliczone typy nie dziedziczą po sobie. To jednak nie istnieje.
foo is T
to nie to samo cofoo.GetType() == typeof(T)
!!źródło
Ja też bym
źródło
Innym sposobem byłoby zdefiniowanie interfejsu IThing, a następnie zaimplementowanie go w obu klasach, oto fragment kodu:
źródło
Jak na C # 7.0 specyfikacji, można zadeklarować zmienną lokalną scoped w sposób
case
tematyceswitch
:Jest to najlepszy sposób na zrobienie czegoś takiego, ponieważ obejmuje po prostu rzutowanie i operacje push-on-the-stack, które są najszybszymi operacjami, jakie interpreter może wykonać tuż po bitowych operacjach i
boolean
warunkach.Porównując to do
Dictionary<K, V>
, tutaj jest o wiele mniejsze zużycie pamięci: trzymanie słownika wymaga więcej miejsca w pamięci RAM i trochę więcej obliczeń przez procesor do utworzenia dwóch tablic (jednej dla kluczy, a drugiej dla wartości) i zebrania kodów mieszających dla kluczy do umieszczenia wartości do odpowiednich kluczy.Tak więc, o ile wiem, nie sądzę, aby istniał szybszy sposób, chyba że chcesz użyć tylko
if
-then
-else
bloku zis
operatorem w następujący sposób:źródło
Możesz tworzyć przeciążone metody:
I rzuć argument, aby
dynamic
wpisać, aby ominąć sprawdzanie typu statycznego:źródło
Udoskonalenia dopasowania wzoru w języku C # 8 umożliwiły to w ten sposób. W niektórych przypadkach spełnia swoje zadanie i jest bardziej zwięzły.
źródło
Szukasz
Discriminated Unions
funkcji języka F #, ale możesz osiągnąć podobny efekt, korzystając z utworzonej przeze mnie biblioteki OneOfhttps://github.com/mcintyre321/OneOf
Główną zaletą w stosunku do
switch
(aif
, aexceptions as control flow
) jest to, że podczas kompilacji bezpieczny - nie ma domyślnego obsługi lub nie mieści sięJeśli dodasz trzeci element do o, pojawi się błąd kompilatora, ponieważ musisz dodać funkcję obsługi Func w wywołaniu przełącznika.
Możesz także wykonać polecenie,
.Match
które zwraca wartość, zamiast wykonać instrukcję:źródło
Utwórz interfejs
IFooable
, a następnie spraw , aby twojaA
iB
klasy zaimplementowały wspólną metodę, która z kolei wywołuje odpowiednią metodę, którą chcesz:Zauważ, że lepiej jest użyć
as
zamiast tego najpierw sprawdzania za pomocą,is
a następnie rzucania, ponieważ w ten sposób wykonujesz 2 rzuty, więc jest to droższe.źródło
W takich przypadkach zwykle kończę na liście predykatów i działań. Coś w tym stylu:
źródło
Po porównaniu opcji przedstawionych tutaj w odpowiedzi na kilka funkcji F #, odkryłem, że F # ma znacznie lepszą obsługę przełączania opartego na typach (chociaż nadal trzymam się C #).
Możesz chcieć zobaczyć tu i tutaj .
źródło
Stworzyłbym interfejs z dowolną nazwą i nazwą metody, która miałaby sens dla twojego przełącznika, nazwijmy je odpowiednio:
IDoable
to mówi o implementacjivoid Do()
.i zmień metodę w następujący sposób:
Przynajmniej jesteś bezpieczny w czasie kompilacji i podejrzewam, że pod względem wydajności jest to lepsze niż sprawdzanie typu w czasie wykonywania.
źródło
Od C # 8 możesz sprawić, że będzie jeszcze bardziej zwięzły dzięki nowemu przełącznikowi. Za pomocą opcji discard _ można uniknąć tworzenia niepotrzebnych zmiennych, gdy nie są potrzebne, na przykład:
Faktura i lista wysyłkowa to klasy, a dokument to obiekt, którym może być dowolna z nich.
źródło
Zgadzam się z Jonem w kwestii posiadania skrótu akcji do nazwy klasy. Jeśli utrzymasz swój wzorzec, możesz zamiast tego rozważyć użycie konstrukcji „as”:
Różnica polega na tym, że gdy używasz tupotu, jeśli (foo to Bar) {((Bar) foo) .Action (); } wykonujesz rzut typu dwa razy. Teraz może kompilator zoptymalizuje i wykona tę pracę tylko raz - ale nie liczyłbym na to.
źródło
Jak sugeruje Pablo, podejście do interfejsu jest prawie zawsze właściwe, aby sobie z tym poradzić. Aby naprawdę skorzystać z przełącznika, inną alternatywą jest posiadanie niestandardowego wyliczenia oznaczającego twój typ na zajęciach.
Jest to również zaimplementowane w BCL. Jednym z przykładów jest MemberInfo.MemberTypes , innym jest
GetTypeCode
dla typów pierwotnych, takich jak:źródło
To jest alternatywna odpowiedź, która łączy wkłady z odpowiedzi JaredPar i VirtLink, z następującymi ograniczeniami:
Stosowanie:
Kod:
źródło
Tak - wystarczy użyć nieco dziwnie nazwanego „dopasowania wzorca” od C # 7 w górę, aby dopasować klasę lub strukturę:
źródło
używam
źródło
Powinien współpracować z
lubić:
źródło
Jeśli znasz klasę, której oczekujesz, ale nadal nie masz obiektu, możesz to zrobić:
źródło