Pisałem ten kod:
private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
switch (criterion.ChangeAction)
{
case BindingType.Inherited:
var action = (byte)ChangeAction.Inherit;
return (x => x.Action == action);
case BindingType.ExplicitValue:
var action = (byte)ChangeAction.SetValue;
return (x => x.Action == action);
default:
// TODO: Localize errors
throw new InvalidOperationException("Invalid criterion.");
}
}
Był zaskoczony, gdy znalazł błąd kompilacji:
Zmienna lokalna o nazwie „akcja” jest już zdefiniowana w tym zakresie
Rozwiązanie problemu było dość łatwe; po prostu pozbycie się drugiego var
rozwiązało problem.
Oczywiście zmienne zadeklarowane w case
blokach mają zakres elementu nadrzędnego switch
, ale jestem ciekawy, dlaczego tak jest. Biorąc pod uwagę, że C # nie zezwala na wykonanie spadać przez innych przypadkach (to wymaga break
, return
, throw
czy goto case
sprawozdanie na koniec każdego case
bloku), wydaje się dość dziwne, że pozwoliłoby to deklaracje zmiennych wewnątrz jeden case
do wykorzystania lub konflikt ze zmiennymi w jakikolwiek inny case
. Innymi słowy, zmienne wydają się przechodzić przez case
instrukcje, nawet jeśli wykonanie nie może. C # bardzo się stara, aby promować czytelność, zakazując niektórych konstrukcji innych języków, które są mylące lub łatwo nadużywane. Ale wydaje się, że to po prostu spowoduje zamieszanie. Rozważ następujące scenariusze:
Gdyby to zmienić na to:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
Pojawia się komunikat „ Zastosowanie nieprzypisanej zmiennej lokalnej„ akcja ”. Jest to mylące, ponieważ w każdym innym konstrukcie w języku C #, o którym mogę pomyśleć
var action = ...
, zainicjuje zmienną, ale tutaj po prostu ją deklaruje.Gdybym zamienił takie przypadki:
case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; return (x => x.Action == action); case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action);
Otrzymuję komunikat „ Nie można użyć zmiennej lokalnej„ akcja ”, zanim nie zostanie zadeklarowana ”. Tak więc kolejność bloków skrzynek wydaje się tutaj ważna w sposób, który nie jest do końca oczywisty - normalnie mógłbym napisać je w dowolnej kolejności, ale chcę,
var
aby pojawił się w pierwszym bloku, w którymaction
jest używany, muszę dostosowaćcase
bloki odpowiednio.Gdyby to zmienić na to:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Wtedy nie dostaję błędu, ale w pewnym sensie wygląda na to, że zmiennej przypisuje się wartość, zanim zostanie zadeklarowana.
(Chociaż nie mogę myśleć o żadnym momencie, w którym naprawdę chciałbyś to zrobić - nawet nie wiedziałem, żegoto case
istniał wcześniej)
Więc moje pytanie brzmi: dlaczego projektanci C # nie dali case
blokom własnego lokalnego zasięgu? Czy są tego jakieś historyczne lub techniczne powody?
źródło
action
zmienną przedswitch
instrukcją lub umieść każdy przypadek w osobnych nawiasach klamrowych, a uzyskasz rozsądne zachowanie.switch
w ogóle - jestem ciekawy uzasadnienia tego projektu.break
nie jest możliwy w C #.Odpowiedzi:
Myślę, że dobrym powodem jest to, że w każdym innym przypadku zakres „normalnej” zmiennej lokalnej jest blokiem ograniczonym przez nawiasy klamrowe (
{}
). Zmienne lokalne, które nie są normalne, pojawiają się w specjalnej konstrukcji przed instrukcją (która zwykle jest blokiem), jakfor
zmienna pętli lub zmienna zadeklarowana wusing
.Jeszcze jednym wyjątkiem są zmienne lokalne w wyrażeniach zapytań LINQ, ale są one całkowicie odmienne od normalnych deklaracji zmiennych lokalnych, więc nie sądzę, aby istniała tam możliwość pomylenia.
W celach informacyjnych reguły znajdują się w § 3.7 Zakresy specyfikacji C #:
(Chociaż nie jestem całkowicie pewien, dlaczego
switch
blok jest wyraźnie wymieniony, ponieważ nie ma żadnej specjalnej składni dla deklaracji zmiennych lokalnych, w przeciwieństwie do wszystkich innych wspomnianych konstrukcji).źródło
switches
wydaje mi się, że zachowują się identycznie pod tym względem (poza wymaganiem przeskoków na końcucase
). Wygląda na to, że to zachowanie zostało po prostu skopiowane stamtąd. Myślę, że krótka odpowiedź brzmi -case
instrukcje nie tworzą bloków, po prostu definiują podziałyswitch
bloku - i dlatego same nie mają zasięgów.Ale tak jest. Możesz tworzyć lokalne zakresy w dowolnym miejscu, zawijając linie
{}
źródło
Zacytuję Erica Lipperta, którego odpowiedź jest dość jasna na ten temat:
Tak więc, o ile nie będziesz bliżej współpracować z zespołem programistów C # z 1999 roku niż Eric Lippert, nigdy nie poznasz dokładnego powodu!
źródło
Wyjaśnienie jest proste - dzieje się tak dlatego, że tak jest w C. Języki takie jak C ++, Java i C # skopiowały składnię i zakres instrukcji switch w celu znajomości.
(Jak stwierdzono w innej odpowiedzi, programiści C # nie mają dokumentacji na temat tego, jak podjęto decyzję dotyczącą zakresów wielkości liter. Jednak nieokreśloną zasadą składni C # było to, że jeśli nie mieli istotnych powodów, aby zrobić to inaczej, skopiowali Javę. )
W C instrukcje przypadków są podobne do goto-label. Instrukcja switch jest naprawdę ładniejszą składnią obliczonego goto . Przypadki określają punkty wejścia do bloku przełączników. Domyślnie reszta kodu zostanie wykonana, chyba że istnieje wyraźne wyjście. Dlatego sensowne jest, że używają tego samego zakresu.
(Zasadniczo. Goto nie są ustrukturyzowane - nie definiują ani nie ograniczają sekcji kodu, definiują jedynie punkty przejścia. Dlatego etykieta goto nie może wprowadzić zakresu.)
C # zachowuje składnię, ale wprowadza zabezpieczenie przed „wypadnięciem”, wymagając wyjścia po każdej (niepustej) klauzuli przypadku. Ale to zmienia nasz sposób myślenia o przełączniku! Sprawy są teraz alternatywnymi gałęziami , podobnie jak gałęzie w if-else. Oznacza to, że spodziewalibyśmy się, że każda gałąź zdefiniuje swój zakres, podobnie jak klauzule if lub klauzule iteracyjne.
Krótko mówiąc: przypadki mają ten sam zakres, ponieważ tak jest w C. Ale wydaje się dziwne i niespójne w C #, ponieważ myślimy o przypadkach jako o alternatywnych gałęziach, a nie o gotowych celach.
źródło
Uproszczonym sposobem patrzenia na zakres jest rozważanie zakresu według bloków
{}
.Ponieważ
switch
nie zawiera żadnych bloków, nie może mieć różnych zasięgów.źródło
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? Nie ma bloków, ale istnieją różne zakresy.for
instrukcję.switch
nie tworzy bloków dla każdego przypadku, tylko na najwyższym poziomie. Podobieństwo polega na tym, że każda instrukcja tworzy jeden blok (liczonyswitch
jako instrukcja).case
?case
nie zawiera bloku, chyba że zdecydujesz się go opcjonalnie dodać.for
trwa dla następnej instrukcji, chyba że dodasz,{}
więc zawsze zawiera blok, alecase
trwa, dopóki coś nie spowoduje opuszczenia prawdziwego bloku,switch
instrukcji.