为什么我需要使用break?

16
我在想为什么C#要求我在switch语句中使用break,即使按定义不允许出现“掉落”语义。因此,编译器可以在每个case块的末尾生成break并节省我的麻烦。
然而,有一种情况(已经在本网站上讨论过)可能是需要明确使用break的原因:
switch (foo) {
    case 0:
    case 1:
        bar();
        break;
    default:
        break;
}

如果foo的值为0或1,则调用bar()方法。

如果编译器会自动生成break语句,那么这个选项会完全崩溃。这就是原因吗?还是有其他好的理由使break是必需的?


8
我个人认为这是C#团队糟糕的设计决策之一,也是他们极少数失败的地方之一。 - Chris Marisic
@Chris: 我强烈建议你阅读被接受答案中链接的Eric的博客。它涵盖了一些我猜想可能让你感到烦恼的事情。 - Sam Harwell
5
你觉得克里斯,你会怎么做呢? - Eric Lippert
为什么C语言中的break不是可选项? - brainimus
5个回答

42
该问题基于一个错误的前提,因此无法回答。语言不需要在switch部分的末尾断开。该语言要求 switch部分的语句列表必须具有无法到达的终点。"break"只是具有此属性的最常用语句。结束switch部分使用"return;", "goto case 2;", "throw new Exception();"或"while (true) {}"(或在某些情况下使用continue,但通常不是好主意)都是完全合法的。

我们要求具有无法到达终点的语句列表是为了强制执行“禁止穿透”规则。

如果您的问题更好地陈述为“当我未能生成具有具有无法到达终点的语句列表的switch部分时,编译器为什么不自动修复我的错误,替我插入一个break语句?”那么答案是C#不是一种“猜测开发人员可能意味着什么并混乱地处理”的语言。有些语言是这样的——例如JScript——但C#不是这样的。

我们认为,犯错的C#程序员希望被告知这些错误,以便他们可以有意识地加以纠正,而这是一件好事。我们的客户告诉我们,他们不希望编译器猜测他们的意图并试图尽力而为。这就是为什么C#也不会像JScript一样猜测您缺少分号的位置,或在您尝试修改只读变量时默默失败,等等。


5
接受的答案提到了Eric的博客,但他自己的回答被踩了?请再投一票支持他。 - SolutionYogi
1
尽管如此,C#编译器对许多事情都有默认值,并在许多情况下试图推断程序员的意图(例如,在类上,方法默认为私有非虚拟)。我认为,在case之后隐式添加break语句即使不是C switch的行为方式,也应该是一个合理的默认值(我这么说是因为我不是C开发人员)。隐式break不应被归类为猜测,它可以是一种明确定义的行为,但我想这可能会让现有的C开发人员感到困惑 :) - Pop Catalin
2
让现有的C开发人员感到困惑是一个特性的很大缺点。我们中的许多开发人员来自C,部分原因是语法表面上非常相似。 - Eric Lippert
1
要不要再加一个自动断开的开关样式呢?但我猜这会给语言设计者带来更多的负担,对吧?也许可以像 caseof (foo) ... case 0 ... case 1 ... 这样,没有 break。 - Joan Venge
4
然后我们将有两种方式来几乎完全相同的事情,这是一项相当古老而笨重的语言特性。调整“switch”并不会以任何方式推进技术的进步。提供两种易混淆的方式来执行相同的操作会导致用户困惑和高昂的测试成本,因此这种情况不太可能发生。 - Eric Lippert
显示剩余4条评论

35
我怀疑C#要求开发人员在每个case末尾放置break或terminal语句的原因是为了清晰明了。这避免了新手误以为C#中的switch()的行为类似于C或C++中的switch,其中会出现fall through行为。在C#中,只有相邻的空case才会出现fall through-这是相对明显的。编辑:实际上,在C#中,fall through总是非法的。然而,合法的是将单个case与两个或更多标签相关联。Eric Lippert详细介绍了这种行为及其与C/C++ switch语句的区别。您可能会对阅读这篇文章在Eric Lipperts博客上感兴趣。

+1 链接到 Eric Lippert 的博客 ;-). 是的,这个设计决策可能是出于清晰度的考虑。 - Uwe Honekamp
它还允许JIT灵活地重新排列代码以进行优化,而不必担心由于跳转而导致出现问题。 - Scott Dorman
@ScottDorman - 这里没有 fall-through(贯穿)……所以如果 break 不是必须的,重新组织代码也不会改变任何东西。如果存在 fall-through,并且代码重新排序可能会导致问题,则无论如何都不需要 break。 - RHSeeger
1
哇塞,他们让C#的switch语句看起来几乎和VB的Select Case一样清晰!但是在我看来,case 0:case 1:有些啰嗦并且容易混淆,相比之下,Case 0, 1更加简洁明了。 - MarkJ

5

如果将break语句变为可选项,就会引入像这样的错误:

switch (thing_result)
{
    case GOOD_THING:
        IssueReward();
        // UH-OH, missing break!
    case BAD_THING:
        IssuePunishment();
        break;
}

C#语言设计者试图帮助程序员避免之前的语言中的陷阱,通常是通过强制程序员更加明确来实现(例如,显式使用“override”或“new”关键字来实现或隐藏虚拟成员)。


0
如果您不需要添加换行符,那么就存在问题。
switch (number)
{
    case 0:
    case 1:
        DoSomething();
}

如果number == 0会发生什么?这是一个不执行任何操作的空情况,还是DoSomething()将被执行?


1
抱歉,但那不是真的。当您在DoSomething()调用后添加一个break时,C#代码将是有效的,并且对于0和1两种情况,DoSomething()方法都将被调用。 - Oliver Hanappi
@Phong,我删除了我的评论,因为其中一部分是正确的。break语句只会增加冗余而不会避免混淆,这一点是正确的。但是在0情况下确实会发生某些事情,就像1情况所做的那样...因为它与1情况共享主体。 - RHSeeger

0

我理解的情况是,它被包含进来是为了匹配C++语法。


6
据我所知,C++不需要使用break语句,因为C++(像C语言一样)允许case语句块穿透。 - Uwe Honekamp

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