Switch case 中额外的花括号有什么作用?

56

我对这个事情很好奇......看例子:

switch(x)
{
    case(a):
        {
        //do stuff
        }
        break;
    case(b):
        //do stuff
        break;
}

我一直像b那样做,但由于C#允许我使用它,而Visual Studio又允许我折叠那个部分,所以我很好奇 - 使用a(带大括号)和b之间的真正区别是什么?


16
除了在情况 A 中定义的变量只在该块中可见外,没有其他区别。 - juergen d
3
没什么特别的。只是有时候你想要创建和使用仅限于某一特定情况的对象。如果不加括号,你在那里定义的任何东西都有更广泛的作用域。 - Robinson
1
不是你要求的,但我发现长的case块很难阅读。如果我需要作用域或任何其他复杂性,它会得到一个新的方法。 - Jay Bazuzi
请参见https://dev59.com/4XA65IYBdhLWcg3wvxaE。 - goodeye
4个回答

78
一对大括号(不是方括号「[]」也不是小括号「()」,而是花括号「{}」)里面有零个或多个语句,在C#中是一种合法的语句,因此可以出现在任何语句可以出现的地方。
正如其他人指出的那样,通常这样做的原因是因为这样的语句引入了一个新的本地变量声明空间,从而定义了其中声明的本地变量的范围。 (回想一下,“作用域”是指元素“可以通过其未限定的名称在程序文本中引用的区域”)。
我注意到这在switch语句中尤其有趣,因为switch的作用域规则有点奇怪。 有关它们有多奇怪的详细信息,请参见我在该主题上的文章中的“Case 3:” http://ericlippert.com/2009/08/13/four-switch-oddities/

我一直感到失望的是,C#没有引入更智能的switch语句,因为C的switch是编程中最奇怪和有问题的结构之一。 - Nate C-K
6
@NateC-K: C#确实引入了一个更好的switch语句!C#设计师们认真研究了C/C++ switch语句的不足之处,并在许多方面进行了改进。例如:(1)“禁止贯穿”规则消除了常见的错误来源。(2)您可以使用字符串进行选择。(3)您可以选择可空类型。(4)案例标签的类型将被检查以与switch的控制类型兼容。(5)“goto case 1”可以正常工作,不像在C++中那样。几乎在每一个可以想象到的方面,C#的switch都是一种改进。 - Eric Lippert
“您可以在字符串中开关”是更为被低估的功能之一。许多编程语言不允许这样做(例如Java或Delphi),这会将一个简单的问题变成一个庞大的if..elseif级联结构。 - Michael Stum
2
@phoog:我向您引荐《C++程序设计语言》第二版3.3.1节(旧版,我知道;但这是我手边有的),其中在第103页上指出:“请注意,case标签不适合用作goto语句的标签。”也许C++在我不注意的时候已经添加了此功能,但1991年我学习C++时它并没有这个功能。 - Eric Lippert
1
@EricLippert:没错,有一些渐进式的改进,我很欣赏它们;C# switch语句绝对更聪明了,因此我之前的说法是不公平的。然而,它仍然保留了奇怪的C语法和随之而来的不适当作用域缺失问题。此外,现在还有额外的规则需要记住,这些规则只适用于switch语句。例如,即使禁止了跌落,您仍必须在每个case中添加一个break,因此这现在纯粹是遗迹性的。 - Nate C-K
我能想到的最明显的改进switch语句的方法是完全放弃基于标签的语法,而采用类似VB的方式;这才是我真正想要的。完全摆脱C语法。我认为C语法唯一的优点就是能够跳转到一个case,但这种用法在最好的情况下也只是边缘的;我认为很难想出任何使用“goto case”无法通过函数分解实现的用途。 - Nate C-K

71

花括号{}用于定义一系列操作的范围。令人惊讶的是,以下代码会编译并运行:

private void ConnectionStateChange(object sender, StateChangeEventArgs e)
{
    string s = "hi";
    switch(s)
    {
        case "hi":
            {
                int a = 1;
                a++;
            }
            {
                int a = 2;
                a++;
            }
            break;
    }

    {
        int a = 1;
        a++;
    }
    {
        int a = 2;
        a++;
    }
}

正如您所看到的,在那个方法中,我创建了四个名为a的变量。每个变量都是完全独立的,因为它们是局部变量,只存在于其自己的作用域内。

这有点讲得通吗?


55
为什么说"bizarrely"?那看起来相当正常(尽管有些牵强)。 - Joey
35
如果在同一个函数中有4个不同的本地变量名为a看起来对你来说很正常,那么你一直在看错误的代码。 :) - cHao
11
因此设计如此,但引入新的局部作用域并不那么不寻常,以我看来。 - Joey
12
这一点不应被视为奇怪。 - jn1kk
2
多么令人遗憾,被接受的答案实际上未能解释(或者显然未能理解)为什么这很有用。 - Konrad Rudolph
显示剩余8条评论

30
它创建了一个新的作用域,在其中你可以创建新的变量。

13

它为您使用的变量创建新的作用域。变量的作用域有时可能很棘手。例如,在您发布的代码中;

switch(x)
{
    case(a):
        {
        int i = 0;
        }
        break;
    case(b):
        i = 1; // Error: The name 'i' doesn't exist in the current context
        break;
}

这里的错误是合理的,因为在case(b)中,变量a超出了作用域。另一方面,

switch(x)
{
    case(a):
        {
        int i = 0;
        }
        break;
    case(b):
        int i = 1; // Error: A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'child' scope to denote something else
        break;
}

上述两个错误看起来是相互矛盾的。为了解决这个问题,你需要在两个case语句中分别定义作用域。
switch(x)
{
    case(a):
        {
        int i = 0;
        }
        break;
    case(b):
        {
        int i = 1; // No error
        }
        break;
}

Eric Lippert分享了一篇非常好的博客链接,以解释在case语句中变量作用域的内容。您应该查看一下。


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