Omówienie instrukcji Switch jest jednym z moich głównych głównych powodów, by kochać switch
kontra if/else if
konstrukty. Tutaj jest przykład:
static string NumberToWords(int number)
{
string[] numbers = new string[]
{ "", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine" };
string[] tens = new string[]
{ "", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety" };
string[] teens = new string[]
{ "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen" };
string ans = "";
switch (number.ToString().Length)
{
case 3:
ans += string.Format("{0} hundred and ", numbers[number / 100]);
case 2:
int t = (number / 10) % 10;
if (t == 1)
{
ans += teens[number % 10];
break;
}
else if (t > 1)
ans += string.Format("{0}-", tens[t]);
case 1:
int o = number % 10;
ans += numbers[o];
break;
default:
throw new ArgumentException("number");
}
return ans;
}
Mądrzy ludzie kulą się, ponieważ string[]
s należy zadeklarować poza funkcją: no cóż, są, to tylko przykład.
Kompilator kończy się niepowodzeniem z następującym błędem:
Kontrola nie może przechodzić z jednej etykiety przypadku („przypadek 3:”) do drugiej Kontrola nie może przechodzić z jednej etykiety przypadku („przypadek 2:”) na inną
Dlaczego? I czy jest jakiś sposób na uzyskanie takiego zachowania bez posiadania trzech if
s?
c#
switch-statement
Matthew Scharley
źródło
źródło
„Dlaczego” ma unikać przypadkowego przewrócenia się, za co jestem wdzięczny. To nierzadkie źródło błędów w C i Javie.
Obejściem tego problemu jest użycie goto, np
Ogólny projekt przełącznika / obudowy jest moim zdaniem trochę niefortunny. Utknął zbyt blisko C - istnieje kilka przydatnych zmian, które można wprowadzić w zakresie określania zakresu itp. Prawdopodobnie pomocny byłby mądrzejszy przełącznik, który mógłby dopasowywać wzorce itp., Ale tak naprawdę zmienia się z przełącznika na „sprawdzanie sekwencji warunków” - w którym momencie może być potrzebne inne imię.
źródło
if (result = true) { }
Przełączanie się jest historycznie jednym z głównych źródeł błędów we współczesnym oprogramowaniu. Projektant języka postanowił wprowadzić obowiązek przeskakiwania na końcu sprawy, chyba że bezpośrednio przechodzi się do następnej sprawy bez przetwarzania.
źródło
case 1, 2:
w językach, które na to pozwalają. Nigdy nie zrozumiem, dlaczego żaden współczesny język nie zdecydowałby się na to.case 1, 2:
jest to jedna etykieta, ale z wieloma nazwami. - FWIW, myślę, że większość języków, które zabraniają upadku, nie zajmuje się specjalnym przypadkiem indu „kolejne etykiety spraw”, ale raczej traktuje etykiety spraw jako adnotację do następnej instrukcji i wymaga ostatniej instrukcji przed instrukcją oznaczoną (jednym lub więcej) etykiety skrzynek na skok.Aby dodać do odpowiedzi tutaj, myślę, że warto rozważyć przeciwne pytanie w związku z tym, a mianowicie. dlaczego C pozwolił przede wszystkim na upadek?
Każdy język programowania służy oczywiście dwóm celom:
Stworzenie dowolnego języka programowania stanowi zatem równowagę między tym, jak najlepiej służyć tym dwóm celom. Z jednej strony, im łatwiej jest zamienić się w instrukcje komputerowe (bez względu na to, czy są to kod maszynowy, kod bajtowy jak IL, czy instrukcje są interpretowane podczas wykonywania), tym bardziej proces kompilacji lub interpretacji będzie bardziej wydajny, niezawodny i kompaktowa moc wyjściowa. W skrajności ten cel powoduje, że po prostu piszemy w asemblerze, IL, a nawet surowe kody operacyjne, ponieważ najłatwiejsza kompilacja jest tam, gdzie w ogóle nie ma kompilacji.
I odwrotnie, im bardziej język wyraża zamiar programisty, a nie środki podjęte w tym celu, tym bardziej zrozumiały program zarówno podczas pisania, jak i podczas konserwacji.
Teraz
switch
zawsze można go było skompilować, przekształcając go w równoważny łańcuchif-else
bloków lub podobny, ale został on zaprojektowany jako umożliwiający kompilację w konkretny wspólny wzorzec składania, w którym bierze się wartość, oblicza przesunięcie od niej (czy patrząc na tabelę indeksowane przez idealny skrót wartości lub przez rzeczywistą arytmetykę wartości *). Warto w tym miejscu zauważyć, że dzisiaj kompilacja w języku C # czasami zamieniaswitch
się w równoważnyif-else
, a czasem stosuje podejście oparte na haszowaniu (podobnie jak w przypadku C, C ++ i innych języków o porównywalnej składni).W takim przypadku istnieją dwa dobre powody, aby pozwolić na awarię:
W każdym razie dzieje się to naturalnie: jeśli zbudujesz tabelę skoków w zestawie instrukcji, a jedna z wcześniejszych partii instrukcji nie zawiera jakiegoś rodzaju skoku lub powrotu, wówczas wykonanie po prostu naturalnie przejdzie do następnej partii. Dopuszczenie upadku było tym, co „po prostu się zdarzy”, jeśli obrócisz
switch
-poprawiający C w skokowy stół-przy użyciu kodu maszynowego.Koderzy, którzy pisali w asemblerze, byli już przyzwyczajeni do ekwiwalentu: podczas pisania tabeli skoków ręcznie w asemblerze musieliby rozważyć, czy dany blok kodu zakończy się zwrotem, skokiem poza tabelę, czy po prostu kontynuować do następnego bloku. W związku z tym koder musi dodać wyraźne
break
razie potrzeby było , było również „naturalne” dla kodera.W tym czasie była to rozsądna próba zrównoważenia dwóch celów języka komputerowego, ponieważ odnosi się to zarówno do wytworzonego kodu maszynowego, jak i do ekspresyjności kodu źródłowego.
Cztery dekady później sprawy nie są takie same z kilku powodów:
switch
, że zostanie ono zmienione,if-else
ponieważ uznano, że podejście może być najbardziej wydajne, lub też zostanie przekształcone w szczególnie ezoteryczny wariant podejścia z tabelą skoków, jest wyższe. Mapowanie między podejściami wyższego i niższego poziomu nie jest tak silne, jak kiedyś.switch
bloków używało przewrotu innego niż wiele etykiet na tym samym bloku i sądzono, że użycie przypadek tutaj oznaczał, że te 3% było w rzeczywistości znacznie wyższe niż normalnie). Tak więc badany język sprawia, że niezwykłe jest łatwiejsze do zaspokojenia niż zwykłe.W odniesieniu do tych dwóch ostatnich punktów rozważ następujący cytat z bieżącej edycji K&R:
Tak więc z pyska konia problematyczne jest przejście przez C. Dobrą praktyką jest zawsze dokumentowanie błędów za pomocą komentarzy, co jest zastosowaniem ogólnej zasady, że należy udokumentować, gdzie robi się coś niezwykłego, ponieważ to potknie się później podczas badania kodu i / lub sprawi, że twój kod będzie wyglądał tak zawiera błąd nowicjusza, gdy jest on poprawny.
A kiedy o tym pomyślisz, kod taki jak ten:
To dodanie czegoś, co wyraźnie pokazuje błąd w kodzie, po prostu nie jest to coś, co może zostać wykryte (lub którego brak może zostać wykryty) przez kompilator.
Jako taki, fakt, że on musi być jawny z przewrotnością w C #, nie dodaje żadnej kary dla ludzi, którzy dobrze pisali w innych językach w stylu C, ponieważ byliby już wyraźni w swoich błędach. †
Wreszcie, użycie
goto
tutaj jest już normą z C i innych takich języków:W przypadku, gdy chcemy, aby blok został uwzględniony w kodzie wykonanym dla wartości innej niż tylko ta, która przenosi go do poprzedniego bloku, to już musimy go użyć
goto
. (Oczywiście istnieją sposoby i sposoby na uniknięcie tego przy różnych warunkach warunkowych, ale dotyczy to prawie wszystkiego, co dotyczy tego pytania). Jako taki C # zbudował na już normalnym sposobie radzenia sobie z jedną sytuacją, w której chcemy trafić więcej niż jeden blok kodu wswitch
, i po prostu uogólniłem go, aby objąć również awarię. Uczyniło to również oba przypadki wygodniejszymi i samo dokumentującymi się, ponieważ musimy dodać nową etykietę w C, ale możemy użyć jejcase
jako etykiety w C #. W języku C # możemy pozbyć siębelow_six
etykiety i używania,goto case 5
co jest wyraźniejsze co do tego, co robimy. (Musielibyśmy również dodaćbreak
dodefault
, które pominąłem, aby powyższy kod C wyraźnie nie był kodem C #).Podsumowując zatem:
break
, dla łatwiejszej nauki języka przez osoby znające podobne języki i łatwiejszego przenoszenia.goto
podejścia do trafiania w ten sam blok z różnychcase
etykiet, co jest używane w C. To po prostu uogólnia go na inne przypadki.goto
oparte na nim podejście jest wygodniejsze i bardziej przejrzyste niż w C, pozwalająccase
, aby instrukcje działały jak etykiety.Podsumowując, całkiem rozsądna decyzja projektowa
* Niektóre formy języka BASIC pozwoliłyby na takie działania,
GOTO (x AND 7) * 50 + 240
które choć kruche, a przez to szczególnie przekonujące w przypadku banowaniagoto
, służą do pokazania wyższego języka odpowiednika tego, w jaki sposób kod niższego poziomu może wykonać skok w oparciu o arytmetyka na wartości, która jest znacznie bardziej rozsądna, gdy jest wynikiem kompilacji, a nie czymś, co należy utrzymywać ręcznie. W szczególności implementacje urządzenia Duffa dobrze nadają się do równoważnego kodu maszynowego lub IL, ponieważ każdy blok instrukcji często ma tę samą długość bez potrzeby dodawanianop
wypełniaczy.† Urządzenie Duffa pojawia się tutaj ponownie, jako rozsądny wyjątek. Fakt, że przy tych i podobnych wzorcach występuje powtarzanie operacji, sprawia, że użycie metody fall-through jest stosunkowo jasne, nawet bez wyraźnego komentarza na ten temat.
źródło
Możesz „goto case label” http://www.blackwasp.co.uk/CSharpGoto.aspx
źródło
Pominęli to zachowanie z założenia, aby uniknąć sytuacji, w których nie był używany przez wolę, ale powodował problemy.
Można go użyć tylko wtedy, gdy w części przypadku nie ma instrukcji, na przykład:
źródło
Zmienili zachowanie instrukcji switch (z C / Java / C ++) dla c #. Wydaje mi się, że powodem było to, że ludzie zapomnieli o upadku i powstały błędy. Jedna z książek, które przeczytałem, mówi, że używam goto do symulacji, ale nie wydaje mi się to dobrym rozwiązaniem.
źródło
goto case
jest. Jego przewaga nad przewrotem polega na tym, że jest wyraźny. To, że niektórzy ludzie sprzeciwiają sięgoto case
tylko pokazaniu, że zostali indoktrynowani przeciwko „goto” bez jakiegokolwiek zrozumienia problemu i nie są w stanie samodzielnie myśleć. Kiedy Dijkstra napisał „GOTO uważane za szkodliwe”, zwracał się do języków, które nie miały żadnych innych możliwości zmiany przepływu kontroli.goto
może być podczas optymalizacji kodu?Możesz osiągnąć upadek jak c ++ przez słowo kluczowe goto.
DAWNY:
źródło
- Dokumentacja C # switch ()
źródło
Po każdym przypadku instrukcja wymaga instrukcji break lub goto , nawet jeśli jest to przypadek domyślny.
źródło
Wystarczy krótka uwaga, aby dodać, że kompilator dla Xamarin rzeczywiście źle to zrobił i pozwala na przewrót. Podobno został naprawiony, ale nie został wydany. Odkryłem to w jakimś kodzie, który faktycznie padał, a kompilator nie narzekał.
źródło
przełącznik (C # Reference) mówi
Musisz także dodać a
break;
do swojejdefault
sekcji, w przeciwnym razie nadal będzie błąd kompilatora.źródło
C # nie obsługuje przechodzenia przez instrukcje switch / case. Nie jestem pewien, dlaczego, ale tak naprawdę nie ma na to wsparcia. Połączenie
źródło
goto case
. Był to celowy, mądry wybór projektantów C #.Zapomniałeś dodać „break”; instrukcja do przypadku 3. W przypadku 2 zapisałeś go w bloku if. Dlatego spróbuj tego:
źródło