16

再次受到一个-5问题的启发!

我读了@Quartermeister的[此评论]并感到惊讶!

那么为什么编译

switch(1) {
    case 2:
}

但事实并非如此。

int i;

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

这也不是

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

更新:

-5问题变成了-3

4

3 回答 3

21

它们都不应该编译。C# 规范要求 switch 部分至少有一个语句。解析器应该禁止它。

让我们忽略解析器允许空语句列表的事实;那不是相关的。规范说switch部分的末端不能有可到达的端点;这是相关的位。

在您的最后一个示例中, switch 部分有一个可到达的端点:

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

所以它一定是一个错误。

如果你有:

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

那么编译器不知道 x 是否永远是 2。它保守地假设它可以,并说该部分有一个可到达的端点,因为 switch case 标签是可到达的。

如果你有

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

然后编译器可以推断端点不可达,因为 case 标签不可达。编译器知道常量 1 永远不会等于常量 2。

如果你有:

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

或者

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

然后你知道,我知道终点不可到达,但编译器不知道。规范中的规则是,可达性只能通过分析常量表达式来确定。任何包含变量的表达式,即使您通过其他方式知道它的值,也不是常量表达式。

过去,C# 编译器存在一些错误,但情况并非如此。你可以这样说:

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

并且编译器会推断 x * 0 必须为 0,因此无法访问 case 标签。那是一个错误,我在 C# 3.0 中修复了它。规范说只有常量用于该分析,并且x是变量,而不是常量。

现在,如果程序是合法的,那么编译器可以使用像这样的高级技术来影响生成的代码。如果你说这样的话:

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

然后编译器可以生成代码,就像你写的一样

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

如果它想要。但它不能使用x * 0 == 0真实的事实来确定语句的可达性。

最后,如果你有

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

那么我们知道这个开关是不可到达的,因此这个块没有一个可到达的端点,所以这令人惊讶地是合法的。但是根据上面的讨论,你现在知道了

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

x * 0 != 0视为false,因此端点被认为是可达的。

于 2013-03-07T21:43:00.230 回答
2

在 Visual Studio 2012 中,第一个原因是显而易见的。编译器确定代码不可访问:

switch (1)
{
    case 2:
}

警告:检测到无法访问的代码。

在另外两种情况下,编译器报告“控制不能从一个案例标签('case 2:')落到另一个”。在任何一个失败的情况下,我都没有看到它说“('case 1')”。

我猜编译器对持续评估并不积极。例如,以下是等价的:

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

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

在这两种情况下,编译器都会尝试生成代码,当它可以进行评估并确定您正在编写的内容是:

switch (1)
{
    case 2:
}

并确定代码不可达。

我怀疑“为什么不编译”的答案将是“因为我们让 JIT 编译器处理积极的优化”。

于 2013-03-07T21:24:02.337 回答
1

好的,所以问题在于编译器完全优化了开关,这里有证据:

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

    switch (1)
    {
        case 2:
    }

    Console.WriteLine("After!");
}

当用 ILSpy 反编译时,它向我们展示了这个 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

它在任何地方都不记得 switch 语句。我认为它没有优化第二个的原因可能与运算符重载和排序有关。因此,我可能有一个自定义类型,当分配给 时1,它会变成2. 但是,我不完全确定,在我看来应该提交错误报告。

于 2013-03-07T20:44:27.030 回答