C#中switch语句中的变量声明

154

C#中的switch语句中,为什么对于在多个case中使用的变量,只在第一个case中声明就可以了呢?

例如,以下代码会报错:"在此范围内已经定义了名为“variable”的局部变量"。

switch (Type)
{
    case Type.A:
            string variable = "x";
                break;
    case Type.B:
            string variable = "y";
                break;
}

然而,根据逻辑,如果类型为Type.B,则不应该命中初始声明。在switch语句中的所有变量是否都存在于单个作用域中,并且它们是在任何逻辑被处理之前创建/分配的?


1
真正丑陋的事情是人们这样做:switch(Type) { case Type.A: string variable = "x"; break; case Type.B: variable = "y"; break; } - giammin
2
@giammin:请详细说明。 - Gabe
2
@zazkapulsk,你应该先声明一个变量,然后在 switch 语句中使用它:string variable = null; switch (Type) { case Type.A: variable = "x"; break; case Type.B: variable = "y"; break; } - giammin
11
我认为一个case不是一个新的作用域是很愚蠢的。加上case x:{...}很丑陋。在switch之前声明变量更加丑陋。我认为这是C#设计中的一个缺陷。 - Jeroen van Langen
6
我无法相信今天学到的内容。我感到难以置信,这太不合逻辑了,我仍然无法相信。对我来说,这是C#的设计缺陷,因为即使没有使用括号,在第一个 case 声明的变量在第二个 case 中也是可见的,这毫无意义。从逻辑上讲,如果第一个 case 不成立,应该不会进入第一个 case 的代码块,这意味着变量在第二个 case 中应该未声明而导致错误。但是不是这样的 :(. 有代码异味吗? - pixel
显示剩余2条评论
7个回答

285

如果您希望将变量限定在特定的情况下,请将该情况放入其自己的代码块中:

switch (Type)
{
    case Type.A:
    {
        string variable = "x";
        /* Do other stuff with variable */
    }
    break;

    case Type.B:
    {
        string variable = "y";
        /* Do other stuff with variable */
    }
    break;
}

10
注意:如果大括号仅包含第一个区块而不包含第二个区块,则此方法无法运行。在这种情况下,尽管作用域不同,第二个“变量”仍会出错。看起来像是一个错误。 - Hi-Angel
3
@Hi-Angel:这不是一个bug。在一个代码块或其嵌套的代码块内,不能声明多个同名的局部变量。实际上,嵌套的代码块“包含”在封闭代码块中声明的局部变量,即使该声明在文件中的词法顺序晚于嵌套块也是如此。这也适用于除switch语句以外的其他结构。请参阅C#语言规范3.3章节。 - Michael Burr
我想我明白了:如果变量存在于块中,则即使嵌套块位于变量声明的更高位置,我也不能在嵌套块中再次使用相同的名称。这是不合理的,应该是一个错误,但是在文档中刻意保留的错误会变成一种特性☺ - Hi-Angel
1
这应该是被接受的答案。 - Kris Craig

50

我认为这与变量的总体范围有关,它是在switch级别定义的块级作用域。

就个人而言,在你的例子中,如果你要将一个值设置在switch内部,为了使其真正受益,你需要在switch外部声明它。


42
遵循大括号。一个变量只存在于最里层的大括号内,其中该变量首次被声明。 - Jarrett Meyer
2
作为一个来自VB世界的人,这是我有点讨厌switch语句的原因之一。其他原因包括在每个case后面必须使用break;,以及没有类似于Case 1, 2, 3Case 4 To 10Case Is > 10的等效语句。 - 41686d6564 stands w. Palestine
@AhmedAbdelhameed,您可以使用switch fall through语句来实现:https://dev59.com/iHVC5IYBdhLWcg3w0EsD - TheBuildGuy
自C#9.0(在2020年11月与.NET 5一起发布)起,C#的switch语句现在可以处理case语句中的区间,例如case > 10:。请参阅有关选择语句的C#语言参考页面:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/selection-statements#the-switch-statement - Simon Elms

42

是的,范围是整个 switch 块——可惜了,在我看来。但是,您可以在单个 case 中添加大括号,以创建更小的作用域。至于它们是否被创建/分配——堆栈框架为方法中的所有局部变量提供了足够的空间(不考虑捕获变量的复杂性)。这个空间并不像在方法执行期间分配那样。


3
恕我直言,尊敬的您,不要建议在switch case代码块中添加作用域。如果您需要为该代码块创建新的作用域,那么很有可能这个代码块做了太多事情。相反,我建议您推荐将处理过程移到函数调用中。 - Randolpho
29
我认为这个说法太笼统了。它可能只是三到四行,但会影响两到三个本地变量,足以让将其重构成一个方法调用很麻烦。 - Jon Skeet
4
同意,我遇到过不止一次这个问题,而且只涉及两三行代码的处理逻辑... 把它放在一个单独的方法中就太麻烦了。 - Philippe
我尝试过这样做,但它会报错,因为变量在子作用域中被声明为其他东西。 - lost_in_the_source
2
@EdwardKarak:仅当您在较高的范围内声明它时才可以。如果您对两种情况都使用大括号,那么就应该没问题了。 - Jon Skeet
显示剩余8条评论

14

因为它们的作用域在开关块内。 C#语言规范指出:

在开关块中声明的局部变量或常量的作用域是开关块。


0

在C#编译器中,变量确实共享作用域。然而,在CIL中,作用域并不存在相同的方式。至于实际的创建/初始化... .NET内存模型允许编译器在遵循简单规则的情况下移动读/写操作,除非该变量被标记为volatile


-2

初始化发生在语句中,但声明实际上是在作用域顶部完成的。 (伪代码)

switch (Type)
{
string variable;

    case Type.A:
            variable = "x";
                break;
    case Type.B:
            variable = "y";
                break;
}

20
我很确定这段代码不起作用。 - Unknown Coder
4
@Jim,是的,我知道这段代码不能运行--这就是为什么我称它为“伪代码”,但它“有效地”完成了任务。 - James Curran
不完全正确。请考虑如果您删除 break,您最终会得到类似以下的内容:string variable = "x"; variable = "y";这是一个过程性的 GOTO:。正确的代码应该像这样:string myVar; switch(myValue) { case MyEnum.A: myVar = "x"; break; case MyEnum.B: myVar = "Y"; break; } - percebus
1
@percebus - 你所说的“正确”是什么意思?在你的例子中,myVar的作用域将超出switch块。我们的观点是,在这些情况下,作用域只是switch块。 - James Curran
与普遍观念相反,switch语句是没有作用域的,不像if语句。这是switch常被认为是“糟糕代码”的主要原因之一(更像是未被理解)。因此,在括号内声明的内容仍然可以在全局作用域中使用。http://msdn.microsoft.com/en-us/library/06tc147t.aspx - percebus
@percebus,你上一条评论里说的东西从哪里来的?在 switch 内部声明的内容在外部是无法访问的。我同意 James 的观点,这个伪代码似乎展示了后台发生的事情。 - Andrew

-2

switch是一种原始的过程化实现,自C时代以来一直存在(甚至早于C++)。

整个switch是一个,作为范围包含的GOTO:(因此在每个case中有:)。如果您参加了一些汇编课程,这可能会很熟悉。

这就是为什么switch在与Enum结合使用并且不在每个单独的case中使用break时最有用的原因。

switch(mood)
{
    case Mood.BORED:
    case Mood.HAPPY:
        drink(oBeer) // will drink if bored OR happy
break;

    case Mood.SAD: // unnecessary but proofs a concept
    default:
        drink(oCoffee)
break;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接