I was writing this code:
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.");
}
}
And was surprised to find a compile error:
A local variable named 'action' is already defined in this scope
It was a pretty easy issue to resolve; just getting rid of the second var
did the trick.
Evidently variables declared in case
blocks have the scope of the parent switch
, but I'm curious as to why this is. Given that C# does not allow execution to fall through other cases (it requires break
, return
, throw
, or goto case
statements at the end of every case
block), it seems quite odd that it would allow variable declarations inside one case
to be used or conflict with variables in any other case
. In other words variables appear to fall through case
statements even though execution cannot. C# takes great pains to promote readability by prohibiting some constructs of other languages that are confusing or or easily abused. But this seems like it's just bound to cause confusion. Consider the following scenarios:
If were to change it to this:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
I get "Use of unassigned local variable 'action'". This is confusing because in every other construct in C# that I can think of
var action = ...
would initialize the variable, but here it simply declares it.If I were to swap the cases like this:
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);
I get "Cannot use local variable 'action' before it is declared". So the order of the case blocks appears to be important here in a way that's not entirely obvious -- Normally I could write these in any order I wish, but because the
var
must appear in the first block whereaction
is used, I have to tweakcase
blocks accordingly.If were to change it to this:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Then I get no error, but in a sense, it looks like the variable is being assigned a value before it's declared.
(Although I can't think of any time you'd actually want to do this -- I didn't even knowgoto case
existed before today)
So my question is, why didn't the designers of C# give case
blocks their own local scope? Are there any historical or technical reasons for this?
5 Answers 5
I think a good reason is that in every other case, the scope of a "normal" local variable is a block delimited by braces ({}
). The local variables that are not normal appear in a special construct before a statement (which is usually a block), like a for
loop variable or a variable declared in using
.
One more exception are local variables in LINQ query expressions, but those are completely different from normal local variable declarations, so I don't think there is a chance of confusion there.
For reference, the rules are in §3.7 Scopes of the C# spec:
The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs.
The scope of a local variable declared in a switch-block of a
switch
statement is the switch-block.The scope of a local variable declared in a for-initializer of a
for
statement is the for-initializer, the for-condition, the for-iterator, and the contained statement of thefor
statement.The scope of a variable declared as part of a foreach-statement, using-statement, lock-statement or query-expression is determined by the expansion of the given construct.
(Though I'm not completely sure why is the switch
block explicitly mentioned, since it doesn't have any special syntax for local variable declations, unlike all the other mentioned constructs.)
-
1+1 Can you provide a link to the scope you're quoting?p.s.w.g– p.s.w.g04/16/2013 00:12:38Commented Apr 16, 2013 at 0:12
-
-
So I looked up the Java specs, and
switches
seem to behave identically in this regard (except for requiring jumps at the end ofcase
's). It seems this behavior was simply copied from there. So I guess the short answer is --case
statements do not create blocks, they simply define divisions of aswitch
block - and therefore do not have scopes by themselves.p.s.w.g– p.s.w.g04/16/2013 17:13:52Commented Apr 16, 2013 at 17:13 -
3Re: I'm not completely sure why is the switch block explicitly mentioned -- the author of the spec is just being picky, implicitly pointing out that a switch-block has a different grammar than a regular block.Eric Lippert– Eric Lippert05/02/2013 00:08:50Commented May 2, 2013 at 0:08
But it does. You can create local scopes anywhere by wrapping lines with {}
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.");
}
I will quote Eric Lippert, which answer is pretty clear on the subject:
A reasonable question is "why is this not legal?" A reasonable answer is "well, why should it be"? You can have it one of two ways. Either this is legal:
switch(y) { case 1: int x = 123; ... break; case 2: int x = 456; ... break; }
or this is legal:
switch(y) { case 1: int x = 123; ... break; case 2: x = 456; ... break; }
but you can't have it both ways. The designers of C# chose the second way as seeming to be the more natural way to do it.
This decision was made on July 7th, 1999, just shy of ten years ago. The comments in the notes from that day are extremely brief, simply stating "A switch-case does not create its own declaration space" and then giving some sample code that shows what works and what does not.
To find out more about what was in the designers minds on this particular day, I'd have to bug a lot of people about what they were thinking ten years ago -- and bug them about what is ultimately a trivial issue; I'm not going to do that.
In short, there is no particularly compelling reason to choose one way or the other; both have merits. The language design team chose one way because they had to pick one; the one they picked seems reasonable to me.
So, unless you are more intime with the C# developer team of 1999 than Eric Lippert, you will never know the exact reason!
-
Not a downvoter, but this was a comment made on the question yesterday.Jesse C. Slicer– Jesse C. Slicer04/17/2013 14:28:15Commented Apr 17, 2013 at 14:28
-
5I see that, but since the commentor doesn't post an answer, I thought it could be a good idea to explicitly create the anwser by quoting the content of the link. And I don't care the downvotes! The OP ask the historical reason, not a "How to do it" answer.Cyril Gandon– Cyril Gandon04/17/2013 14:34:22Commented Apr 17, 2013 at 14:34
The explanation is simple - it is because it is like that in C. Languages like C++, Java and C# have copied the syntax and scoping of the switch statement for the sake of familiarity.
(As stated in another answer, the developers of C# does not have documentation about how this decision regarding the case-scopes was made. But the unstated principle for C# syntax was that unless they had compelling reasons to do it differently, they copied Java.)
In C, the case statements are similar to goto-labels. The switch statement is really a nicer syntax for a computed goto. The cases define the entry points into the switch block. By default the rest of the code will be executed, unless there is an explicit exit. So it only makes sense they use the same scope.
(More fundamentally. Goto's are not structured - they do not define or delimit sections of code, they only defines jump-points. Therefore a goto label cannot introduce a scope.)
C# retains the syntax, but introduces a safeguard against "fall through" by requiring an exit after each (non-empty) case clause. But this changes how we think of the switch! The cases are now alternate branches, like the branches in an if-else. This means we would expect each branch to defines its own scope just like if-clauses or iteration clauses.
So in short: Cases share the same scope because this is how it is in C. But it seems weird and inconsistent in C# because we think of cases as alternate branches rather than goto targets.
-
2"it seems weird and inconsistent in C# because we think of cases as alternate branches rather than goto targets" That's precisely the confusion I had. +1 for explaining the aesthetic reasons behind why it feels wrong in C#.p.s.w.g– p.s.w.g10/06/2017 16:46:36Commented Oct 6, 2017 at 16:46
A simplified way of looking at scope is to consider scope by blocks {}
.
Since switch
does not contain any blocks, it cannot have different scopes.
-
3What about
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? There are no blocks, but there are different scopes.svick– svick04/15/2013 23:54:26Commented Apr 15, 2013 at 23:54 -
@svick: As I said, simplified, there is a block, created by the
for
statement.switch
doesn't create blocks for each case, just at the top level. The similarity is that each statement creates one block (countingswitch
as the statement).Guvante– Guvante04/16/2013 00:26:21Commented Apr 16, 2013 at 0:26 -
1Then why couldn't you count
case
instead?svick– svick04/16/2013 00:28:33Commented Apr 16, 2013 at 0:28 -
2@svick: Because
case
doesn't contain a block, unless you decide to optionally add one.for
lasts for the next statement unless you add{}
so it always has a block, butcase
lasts until something causes you to leave the real block, theswitch
statement.Guvante– Guvante04/16/2013 16:50:03Commented Apr 16, 2013 at 16:50
action
variable before theswitch
statement, or put each case into its own braces, and you will get sensible behavior.switch
at all -- I'm just curious about the reasoning behind this design.switch
semantics from C / Java directly, and later made the decision to makecase
blocks more idiot proof by requiring jumps at the end, but they simply didn't feel the need to go one step further in idiot-proofing the identifier scope as well.