Domanda

Inspired from a -5 question again!

I read [this comment] of @Quartermeister and been astonished!

So why this compiles

switch(1) {
    case 2:
}

but this doesn't.

int i;

switch(i=1) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

neither this

switch(2) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

update:

The -5 question became -3.

È stato utile?

Soluzione

None of them should compile. The C# specification requires that a switch section have at least one statement. The parser should disallow it.

Let's ignore the fact that the parser allows an empty statement list; that's not what's relevant. The specification says that the end of the switch section must not have a reachable end point; that's the relevant bit.

In your last example, the switch section has a reachable end point:

void M(int x) { switch(2) { case 2: ; } }

so it must be an error.

If you had:

void M(int x) { switch(x) { case 2: ; } }

then the compiler does not know if x will ever be 2. It assumes conservatively that it could, and says that the section has a reachable end point, because the switch case label is reachable.

If you had

void M(int x) { switch(1) { case 2: ; } }

Then the compiler can reason that the endpoint is not reachable because the case label is not reachable. The compiler knows that the constant 1 is never equal to the constant 2.

If you had:

void M(int x) { switch(x = 1) { case 2: ; } }

or

void M(int x) { x = 1; switch(x) { case 2: ; } }

Then you know and I know that the end point is not reachable, but the compiler does not know that. The rule in the specification is that reachability is only determined by analyzing constant expressions. Any expression which contains a variable, even if you know its value by some other means, is not a constant expression.

In the past the C# compiler had bugs where this was not the case. You could say things like:

void M(int x) { switch(x * 0) { case 2: ; } }

and the compiler would reason that x * 0 had to be 0, therefore the case label is not reachable. That was a bug, which I fixed in C# 3.0. The specification says that only constants are used for that analysis, and x is a variable, not a constant.

Now, if the program is legal then the compiler can use advanced techniques like this to influence what code is generated. If you say something like:

void M(int x) { if (x * 0 == 0) Y(); }

Then the compiler can generate the code as though you'd written

void M(int x) { Y(); }

if it wants. But it cannot use the fact that x * 0 == 0 is true for the purposes of determining statement reachability.

Finally, if you have

void M(int x) { if (false) switch(x) { case 2: ; } }

then we know that the switch is not reachable, therefore the block does not have a reachable end point, so this is, surprisingly, legal. But given the discussion above, you now know that

void M(int x) { if (x * 0 != 0) switch(x) { case 2: ; } }

does not treat x * 0 != 0 as false, so the end point is considered reachable.

Altri suggerimenti

In Visual Studio 2012, the reason for the first is obvious. The compiler determines that the code is unreachable:

switch (1)
{
    case 2:
}

Warning: Unreachable code detected.

In the other two cases, the compiler reports "Control cannot fall through from one case label ('case 2:') to another". I do not see it saying "('case 1')" in either of the failing cases.

I guess the compiler just isn't aggressive about constant evaluation. For example, the following are equivalent:

int i;
switch(i=1)
{
    case 2:
}

and

int i = 1;
switch(i)
{
    case 2:
}

In both cases, the compiler attempts to generate code, when it could do the evaluation and determine that what you're writing is:

switch (1)
{
    case 2:
}

And determine that the code is unreachable.

I suspect the "why doesn't this compile" answer will be "because we let the JIT compiler handle aggressive optimization."

Alright, so the problem with this is that the compiler completely optimizes away the switch, and here's proof:

static void withoutVar()
{
    Console.WriteLine("Before!");

    switch (1)
    {
        case 2:
    }

    Console.WriteLine("After!");
}

Which, when decompiled with ILSpy, shows us this IL:

.method private hidebysig static 
    void withoutVar () cil managed 
{
    // Method begins at RVA 0x2053
    // Code size 26 (0x1a)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldstr "Before!"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: br.s IL_000e

    IL_000e: ldstr "After!"
    IL_0013: call void [mscorlib]System.Console::WriteLine(string)
    IL_0018: nop
    IL_0019: ret
} // end of method Program::withoutVar

Which has no recollection of a switch statement anywhere. I think that the reason it doesn't optimize away the second one as well may have something to do with operator overloading and the sort. So, it could be possible that I have a custom type that when assigned to 1, it turns into 2. However, I'm not entirely sure, seems to me like a bug report should be submitted.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top