Ich habe diesen Code geschrieben:
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.");
}
}
Und war überrascht, einen Kompilierungsfehler zu finden:
In diesem Bereich ist bereits eine lokale Variable mit dem Namen 'action' definiert
Es war ein ziemlich leicht zu lösendes Problem. Nur den zweiten loszuwerden, war var
der Trick.
In case
Blöcken deklarierte Variablen haben offensichtlich den Gültigkeitsbereich des übergeordneten Elements switch
, aber ich bin gespannt, warum dies so ist. Da C # nicht Ausführung ermöglichen , durch andere Fälle zu fallen (es erfordert break
, return
, throw
, oder goto case
Aussagen am Ende eines jeden case
Blocks), so scheint es ziemlich merkwürdig , dass es Variablendeklarationen erlauben würde , innerhalb eines case
zu verwendenden oder Konflikt mit Variablen in jedem anderen case
. Mit anderen Worten, Variablen scheinen durch case
Anweisungen zu fallen , obwohl dies bei der Ausführung nicht möglich ist. C # ist sehr bemüht, die Lesbarkeit zu fördern, indem es einige Konstrukte anderer Sprachen verbietet, die verwirrend sind oder leicht missbraucht werden. Aber das scheint nur Verwirrung zu stiften. Stellen Sie sich die folgenden Szenarien vor:
Wenn es so geändert werden soll:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
Ich erhalte die Meldung " Verwendung der nicht zugewiesenen lokalen Variablen 'action' ". Dies ist verwirrend, weil in jedem anderen Konstrukt in C #, das mir
var action = ...
einfällt, die Variable initialisiert wird, aber hier wird sie einfach deklariert.Wenn ich die Fälle so vertauschen würde:
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);
Ich erhalte die Meldung "Die lokale Variable 'action' kann nicht verwendet werden, bevor sie deklariert wurde ". Die Reihenfolge der Case-Blöcke scheint hier also in einer Weise wichtig zu sein, die nicht ganz offensichtlich ist. Normalerweise könnte ich diese in einer beliebigen Reihenfolge schreiben, aber da die
var
im ersten Block erscheinen müssen, in dem sieaction
verwendet werden, muss ich diecase
Blöcke anpassen entsprechend.Wenn es so geändert werden soll:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Dann erhalte ich keine Fehlermeldung, aber in gewissem Sinne sieht es so aus, als würde der Variablen ein Wert zugewiesen, bevor sie deklariert wird.
(Obwohl mir keine Zeit einfällt, zu der Sie das tatsächlich tun möchten - ich wusste nicht einmal, dassgoto case
es das schon heute gab)
Meine Frage ist also, warum haben die Designer von C # den case
Blöcken nicht ihren eigenen lokalen Geltungsbereich gegeben? Gibt es dafür historische oder technische Gründe?
quelle
action
Variable vor derswitch
Anweisung, oder setzen Sie jeden Fall in eine eigene Klammer, und Sie erhalten ein vernünftiges Verhalten.switch
zu verwenden. Ich bin nur neugierig auf die Gründe für dieses Design.break
, in C # nicht möglich ist.Antworten:
Ich denke, ein guter Grund ist, dass in jedem anderen Fall der Gültigkeitsbereich einer „normalen“ lokalen Variablen ein durch geschweifte Klammern (
{}
) begrenzter Block ist . Die lokalen Variablen, die nicht normal sind, werden in einem speziellen Konstrukt vor einer Anweisung (die normalerweise ein Block ist) angezeigt, wie z. B. einefor
Schleifenvariable oder eine in deklarierte Variableusing
.Eine weitere Ausnahme bilden lokale Variablen in LINQ-Abfrageausdrücken, die sich jedoch völlig von normalen Deklarationen lokaler Variablen unterscheiden, sodass hier keine Verwechslungsgefahr besteht.
Zu Referenzzwecken finden Sie die Regeln in §3.7 Geltungsbereich der C # -Spezifikation:
(Obwohl ich nicht ganz sicher bin, warum der
switch
Block explizit erwähnt wird, da er im Gegensatz zu allen anderen erwähnten Konstrukten keine spezielle Syntax für lokale Variablendeklarationen hat.)quelle
switches
in dieser Hinsicht anscheinend identisch verhalten (abgesehen davon, dass am Ende voncase
's Sprünge erforderlich sind ). Es scheint, dass dieses Verhalten einfach von dort kopiert wurde. Ich denke, die kurze Antwort lautet:case
Anweisungen erzeugen keine Blöcke, sie definieren lediglich Teilungen einesswitch
Blocks und haben daher keinen eigenen Gültigkeitsbereich.Aber es tut. Sie können überall lokale Bereiche erstellen, indem Sie Zeilen mit umbrechen
{}
quelle
Ich zitiere Eric Lippert, dessen Antwort zu diesem Thema ziemlich klar ist:
Wenn Sie mit dem C # -Entwicklerteam von 1999 nicht so vertraut sind wie Eric Lippert, werden Sie den genauen Grund nie erfahren!
quelle
Die Erklärung ist einfach - es liegt daran, dass es in C so ist. Sprachen wie C ++, Java und C # haben die Syntax und den Gültigkeitsbereich der switch-Anweisung aus Gründen der Vertrautheit kopiert.
(Wie in einer anderen Antwort angegeben, haben die Entwickler von C # keine Dokumentation darüber, wie diese Entscheidung in Bezug auf die Anwendungsbereiche getroffen wurde. Das nicht angegebene Prinzip für die C # -Syntax war jedoch, dass sie Java kopierten, es sei denn, sie hatten zwingende Gründe, dies anders zu tun. )
In C ähneln die case-Anweisungen goto-labels. Die switch-Anweisung ist wirklich eine schönere Syntax für ein berechnetes goto . Die Fälle definieren die Eintrittspunkte in den Schaltblock. Standardmäßig wird der Rest des Codes ausgeführt, sofern es keinen expliziten Exit gibt. Daher ist es nur sinnvoll, dass sie denselben Bereich verwenden.
(. Grundsätzlicher Gotos sind nicht strukturiert - sie definieren nicht oder abgrenzen Abschnitte des Codes, sie definiert nur jump-Punkte Daher ist eine goto - Label. Nicht kann einen Rahmen vorstellen.)
C # behält die Syntax bei, bietet jedoch eine Absicherung gegen "Durchfallen", indem nach jeder (nicht leeren) case-Klausel ein Exit erforderlich ist. Aber das ändert unsere Einstellung zum Schalter! Die Fälle sind jetzt alternative Zweige , wie die Zweige in einem If-else. Dies bedeutet, dass wir erwarten, dass jeder Zweig seinen eigenen Bereich definiert, genau wie If-Klauseln oder Iterationsklauseln.
Kurz gesagt: Fälle haben den gleichen Gültigkeitsbereich, da dies in C der Fall ist. In C # erscheint dies jedoch seltsam und inkonsistent, da wir Fälle als alternative Verzweigungen und nicht als Ziele betrachten.
quelle
Eine vereinfachte Sichtweise auf den Geltungsbereich besteht darin, den Geltungsbereich blockweise zu betrachten
{}
.Da
switch
es keine Blöcke enthält, kann es keine unterschiedlichen Bereiche haben.quelle
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? Es gibt keine Blöcke, aber es gibt verschiedene Bereiche.for
Anweisung erzeugt wird.switch
erstellt keine Blöcke für jeden Fall, nur auf der obersten Ebene. Die Ähnlichkeit besteht darin, dass jede Anweisung einen Block erstellt (giltswitch
als Anweisung).case
stattdessen zählen ?case
es keinen Block enthält, es sei denn, Sie möchten optional einen hinzufügen.for
Dauert für die nächste Anweisung, es sei denn, Sie fügen{}
sie hinzu, sodass sie immer einen Block enthält.case
Dauert jedoch so lange, bis Sie den eigentlichen Block, dieswitch
Anweisung, verlassen.