简体   繁体   English

C# 中的 Switch 语句失败?

[英]Switch statement fallthrough in C#?

Switch statement fallthrough is one of my personal major reasons for loving switch vs. if/else if constructs. Switch 语句失败是我个人喜欢switchif/else if结构的主要原因之一。 An example is in order here:这里有一个例子:

static string NumberToWords(int number)
{
    string[] numbers = new string[] 
        { "", "one", "two", "three", "four", "five", 
          "six", "seven", "eight", "nine" };
    string[] tens = new string[] 
        { "", "", "twenty", "thirty", "forty", "fifty", 
          "sixty", "seventy", "eighty", "ninety" };
    string[] teens = new string[]
        { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
          "sixteen", "seventeen", "eighteen", "nineteen" };

    string ans = "";
    switch (number.ToString().Length)
    {
        case 3:
            ans += string.Format("{0} hundred and ", numbers[number / 100]);
        case 2:
            int t = (number / 10) % 10;
            if (t == 1)
            {
                ans += teens[number % 10];
                break;
            }
            else if (t > 1)
                ans += string.Format("{0}-", tens[t]);
        case 1:
            int o = number % 10;
            ans += numbers[o];

            break;
        default:
            throw new ArgumentException("number");
    }
    return ans;
}

The smart people are cringing because the string[] s should be declared outside the function: well, they are, this is just an example.聪明的人害怕,因为string[]应该在函数之外声明:嗯,他们是,这只是一个例子。

The compiler fails with the following error:编译器失败并出现以下错误:

Control cannot fall through from one case label ('case 3:') to another
Control cannot fall through from one case label ('case 2:') to another

Why?为什么? And is there any way to get this sort of behaviour without having three if s?有没有办法在没有三个if的情况下获得这种行为?

(Copy/paste of an answer I provided elsewhere ) (复制/粘贴我在其他地方提供答案

Falling through switch - case s can be achieved by having no code in a case (see case 0 ), or using the special goto case (see case 1 ) or goto default (see case 2 ) forms: 通过switch - case下降可以通过在一个case中没有代码(参见case 0 ),或使用特殊的goto case (见case 1 )或goto default (见case 2 )形式来实现:

switch (/*...*/) {
    case 0: // shares the exact same code as case 1
    case 1:
        // do something
        goto case 2;
    case 2:
        // do something else
        goto default;
    default:
        // do something entirely different
        break;
}

The "why" is to avoid accidental fall-through, for which I'm grateful. “为什么”是为了避免意外跌倒,对此我感激不尽。 This is a not uncommon source of bugs in C and Java. 这是C和Java中不常见的错误来源。

The workaround is to use goto, eg 解决方法是使用goto,例如

switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and ", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

The general design of switch/case is a little bit unfortunate in my view. 在我看来,开关/外壳的总体设计有点不幸。 It stuck too close to C - there are some useful changes which could be made in terms of scoping etc. Arguably a smarter switch which could do pattern matching etc would be helpful, but that's really changing from switch to "check a sequence of conditions" - at which point a different name would perhaps be called for. 它离C太近 - 有一些有用的变化可以在范围等方面进行。可以说,一个更聪明的开关可以进行模式匹配等会有所帮助,但这确实从开关变为“检查一系列条件” - 此时可能会要求使用其他名称。

Switch fallthrough is historically one of the major source of bugs in modern softwares. 交换机漏洞历史上是现代软件中错误的主要来源之一。 The language designer decided to make it mandatory to jump at the end of the case, unless you are defaulting to the next case directly without processing. 语言设计者决定强制要求在案例结束时跳转,除非您在没有处理的情况下直接默认为下一个案例。

switch(value)
{
    case 1:// this is still legal
    case 2:
}

To add to the answers here, I think it's worth considering the opposite question in conjunction with this, viz. 为了在这里添加答案,我认为值得考虑与此相关的相反问题,即。 why did C allow fall-through in the first place? 为什么C首先允许掉线?

Any programming language of course serves two goals: 任何编程语言当然都有两个目标:

  1. Provide instructions to the computer. 向计算机提供说明。
  2. Leave a record of the intentions of the programmer. 留下程序员的意图记录。

The creation of any programming language is therefore a balance between how to best serve these two goals. 因此,任何编程语言的创建都是如何最好地服务于这两个目标之间的平衡。 On the one hand, the easier it is to turn into computer instructions (whether those are machine code, bytecode like IL, or the instructions are interpreted on execution) then more able that process of compilation or interpretation will be to be efficient, reliable and compact in output. 一方面,变得更容易变成计算机指令(无论是机器代码,字节码如IL,还是指令在执行时被解释),那么编译或解释过程将更加高效,可靠和紧凑的输出。 Taken to its extreme, this goal results in our just writing in assembly, IL, or even raw op-codes, because the easiest compilation is where there is no compilation at all. 尽管如此,这个目标导致我们只是在汇编,IL或甚至原始操作码中编写,因为最简单的编译是根本没有编译的地方。

Conversely, the more the language expresses the intention of the programmer, rather than the means taken to that end, the more understandable the program both when writing and during maintenance. 相反,语言表达程序员的意图越多,而不是为此目的采取的手段,在编写和维护期间程序就越容易理解。

Now, switch could always have been compiled by converting it into the equivalent chain of if-else blocks or similar, but it was designed as allowing compilation into a particular common assembly pattern where one takes a value, computes an offset from it (whether by looking up a table indexed by a perfect hash of the value, or by actual arithmetic on the value*). 现在, switch总是可以通过将其转换为等效的if-else块或类似链来编译,但它被设计为允许编译成特定的公共汇编模式,其中一个取值,计算它的偏移量(无论是查找由值的完美哈希索引的表,或通过值*的实际算术查找。 It's worth noting at this point that today, C# compilation will sometimes turn switch into the equivalent if-else , and sometimes use a hash-based jump approach (and likewise with C, C++, and other languages with comparable syntax). 在这一点上值得注意的是,今天,C#编译有时会switch为等效的if-else ,有时会使用基于散列的跳转方法(同样使用C,C ++和其他具有可比语法的语言)。

In this case there are two good reasons for allowing fall-through: 在这种情况下,有两个很好的理由允许掉期:

  1. It just happens naturally anyway: if you build a jump table into a set of instructions, and one of the earlier batches of instructions doesn't contain some sort of jump or return, then execution will just naturally progress into the next batch. 它无论如何都是自然发生的:如果你将一个跳转表构建成一组指令,并且其中一个早期批量指令不包含某种跳转或返回,那么执行将自然地进入下一批。 Allowing fall-through was what would "just happen" if you turned the switch -using C into jump-table–using machine code. 如果你将switch成使用机器码的跳转表,那么允许掉线就是“刚刚发生”。

  2. Coders who wrote in assembly were already used to the equivalent: when writing a jump table by hand in assembly, they would have to consider whether a given block of code would end with a return, a jump outside of the table, or just continue on to the next block. 在汇编中编写的编码器已经习惯了等价物:当在汇编中手动编写跳转表时,他们必须考虑给定的代码块是以返回结束,跳出表格还是继续到下一个街区。 As such, having the coder add an explicit break when necessary was "natural" for the coder too. 因此,让编码器在必要时添加明确的break对于编码器来说也是“自然的”。

At the time therefore, it was a reasonable attempt to balance the two goals of a computer language as it relates to both the produced machine code, and the expressiveness of the source code. 因此,在计算机语言的两个目标之间进行平衡是合理的尝试,因为它与生成的机器代码和源代码的表达性有关。

Four decades later though, things are not quite the same, for a few reasons: 四十年后,情况并不完全相同,原因有以下几点:

  1. Coders in C today may have little or no assembly experience. 今天C中的编码员可能很少或根本没有装配经验。 Coders in many other C-style languages are even less likely to (especially Javascript!). 许多其他C风格语言的编码器甚至不太可能(尤其是Javascript!)。 Any concept of "what people are used to from assembly" is no longer relevant. 任何“人们习惯于装配”的概念都不再相关。
  2. Improvements in optimisations mean that the likelihood of switch either being turned into if-else because it was deemed the approach likely to be most efficient, or else turned into a particularly esoteric variant of the jump-table approach are higher. 优化的改进意味着switch的可能性要么变成if-else因为它被认为是最有效的方法,或者转变为跳转表方法的特别深奥的变体更高。 The mapping between the higher- and lower-level approaches is not as strong as it once was. 较高级别和较低级别方法之间的映射不像以前那样强大。
  3. Experience has shown that fall-through tends to be the minority case rather than the norm (a study of Sun's compiler found 3% of switch blocks used a fall-through other than multiple labels on the same block, and it was thought that the use-case here meant that this 3% was in fact much higher than normal). 经验表明,跌倒往往是少数情况而不是常态(对Sun的编译器的研究发现,3%的switch块在同一块上使用了除了多个标签之外的掉落,并且认为使用 - 这里的例子意味着这3%实际上远高于正常水平)。 So the language as studied make the unusual more readily catered-to than the common. 因此,所研究的语言使得不寻常的事情比普通的更容易照顾。
  4. Experience has shown that fall-through tends to be the source of problems both in cases where it is accidentally done, and also in cases where correct fall-through is missed by someone maintaining the code. 经验表明,在意外完成的情况下,以及在维护代码的人错过正确的漏报的情况下,跌倒往往是问题的根源。 This latter is a subtle addition to the bugs associated with fall-through, because even if your code is perfectly bug-free, your fall-through can still cause problems. 后者是与掉落相关的错误的一个微妙的补充,因为即使您的代码完全没有错误,您的堕落仍然可能导致问题。

Related to those last two points, consider the following quote from the current edition of K&R: 与最后两点相关,请考虑当前版本的K&R中的以下引用:

Falling through from one case to another is not robust, being prone to disintegration when the program is modified. 从一个案例落到另一个案例并不健全,在修改程序时容易崩溃。 With the exception of multiple labels for a single computation, fall-throughs should be used sparingly, and commented. 除了单个计算的多个标签之外,应谨慎使用漏洞并进行评论。

As a matter of good form, put a break after the last case (the default here) even though it's logically unnecessary. 作为一个好的形式,在最后一个案例(默认在这里)之后休息,即使它在逻辑上是不必要的。 Some day when another case gets added at the end, this bit of defensive programming will save you. 有一天,当最后添加另一个案例时,这一点防御性编程将为您节省开支。

So, from the horse's mouth, fall-through in C is problematic. 因此,从马的口中,C中的跌落是有问题的。 It's considered good practice to always document fall-throughs with comments, which is an application of the general principle that one should document where one does something unusual, because that's what will trip later examination of the code and/or make your code look like it has a novice's bug in it when it is in fact correct. 总是记录带有注释的漏洞是一种良好的做法,这是应用一般原则的应用,应该记录一个人做一些不寻常的事情,因为这将是后来检查代码和/或使你的代码看起来像什么的当它实际上是正确的时,它有一个新手的错误。

And when you think about it, code like this: 当你考虑它时,代码如下:

switch(x)
{
  case 1:
   foo();
   /* FALLTHRU */
  case 2:
    bar();
    break;
}

Is adding something to make the fall-through explicit in the code, it's just not something that can be detected (or whose absence can be detected) by the compiler. 添加的东西,使落空在代码中明确的,它只是没有东西可以被检测(或者其缺乏可检测)的编译器。

As such, the fact that on has to be explicit with fall-through in C# doesn't add any penalty to people who wrote well in other C-style languages anyway, since they would already be explicit in their fall-throughs.† 因此,事实上必须明确表示C#中的堕落并不会给那些在其他C风格的语言中写得很好的人增加任何惩罚,因为他们已经在他们的堕落中明确表示。†

Finally, the use of goto here is already a norm from C and other such languages: 最后, goto的使用已经成为C和其他类似语言的标准:

switch(x)
{
  case 0:
  case 1:
  case 2:
    foo();
    goto below_six;
  case 3:
    bar();
    goto below_six;
  case 4:
    baz();
    /* FALLTHRU */
  case 5:
  below_six:
    qux();
    break;
  default:
    quux();
}

In this sort of case where we want a block to be included in the code executed for a value other than just that which brings one to the preceding block, then we're already having to use goto . 在这种情况下,我们希望将一个块包含在为除前一个块之外的值执行的代码中执行的代码中,然后我们必须使用goto (Of course, there are means and ways of avoiding this with different conditionals but that's true of just about everything relating to this question). (当然,有一些方法和方法可以通过不同的条件避免这种情况,但对于与这个问题相关的所有事情都是如此)。 As such C# built on the already normal way to deal with one situation where we want to hit more than one block of code in a switch , and just generalised it to cover fall-through as well. 因此,C#建立在已经正常的方式来处理我们想要在switch击中多个代码块的一种情况,并且只是将其概括为覆盖掉落。 It also made both cases more convenient and self-documenting, since we have to add a new label in C but can use the case as a label in C#. 它还使两个案例更方便和自我记录,因为我们必须在C中添加新标签,但可以将case用作C#中的标签。 In C# we can get rid of the below_six label and use goto case 5 which is clearer as to what we are doing. 在C#中,我们可以删除below_six标签并使用goto case 5 ,它更清楚我们正在做什么。 (We'd also have to add break for the default , which I left out just to make the above C code clearly not C# code). (我们还必须为default添加break ,我只是为了使上面的C代码显然不是C#代码而遗漏了)。

In summary therefore: 因此总结如下:

  1. C# no longer relates to unoptimised compiler output as directly as C code did 40 years ago (nor does C these days), which makes one of the inspirations of fall-through irrelevant. C#不再像40年前的C代码那样直接与未经优化的编译器输出相关(这些天也不是C),这使得堕落的灵感之一无关紧要。
  2. C# remains compatible with C in not just having implicit break , for easier learning of the language by those familiar with similar languages, and easier porting. C#与C语言保持兼容,不仅具有隐式break ,因为熟悉类似语言的人更容易学习语言,并且更容易移植。
  3. C# removes a possible source of bugs or misunderstood code that has been well-documented as causing problems for the last four decades. C#删除了一个可能的错误来源或被误解的代码,这些代码在过去四十年中已被充分记录为导致问题。
  4. C# makes existing best-practice with C (document fall through) enforceable by the compiler. C#通过编译器强制执行C(文档落实)现有的最佳实践。
  5. C# makes the unusual case the one with more explicit code, the usual case the one with the code one just writes automatically. C#使得不常见的情况是具有更明确代码的情况,通常情况下代码之一只是自动写入。
  6. C# uses the same goto -based approach for hitting the same block from different case labels as is used in C. It just generalises it to some other cases. C#使用相同的基于goto的方法从C中使用的不同case标签中命中相同的块。它只是将其推广到其他一些情况。
  7. C# makes that goto -based approach more convenient, and clearer, than it is in C, by allowing case statements to act as labels. 通过允许case语句作为标签,C#使得基于goto的方法比在C中更方便,更清晰。

All in all, a pretty reasonable design decision 总而言之,这是一个非常合理的设计决策


*Some forms of BASIC would allow one to do the likes of GOTO (x AND 7) * 50 + 240 which while brittle and hence a particularly persuasive case for banning goto , does serve to show a higher-language equivalent of the sort of way that lower-level code can make a jump based on arithmetic upon a value, which is much more reasonable when it's the result of compilation rather than something that has to be maintained manually. *某些形式的BASIC可以让人们做GOTO (x AND 7) * 50 + 240 ,虽然脆弱,因此禁止goto特别有说服力的情况,确实有助于显示更高语言的等效方式较低级别的代码可以根据对值的算术进行跳转,这在编译结果时更合理,而不是必须手动维护。 Implementations of Duff's Device in particular lend themselves well to the equivalent machine code or IL because each block of instructions will often be the same length without needing the addition of nop fillers. Duff设备的实现尤其适用于等效的机器代码或IL,因为每个指令块通常具有相同的长度而无需添加nop填充器。

†Duff's Device comes up here again, as a reasonable exception. †Duff的设备再次出现在这里,作为一个合理的例外。 The fact that with that and similar patterns there's a repetition of operations serves to make the use of fall-through relatively clear even without an explicit comment to that effect. 事实上,即使没有对这种效果的明确评论,使用这种和相似的模式重复操作也可以使得使用跌倒相对清晰。

You can 'goto case label' http://www.blackwasp.co.uk/CSharpGoto.aspx 你可以'转到案例标签' http://www.blackwasp.co.uk/CSharpGoto.aspx

The goto statement is a simple command that unconditionally transfers the control of the program to another statement. goto语句是一个简单的命令,它无条件地将程序的控制权转移到另一个语句。 The command is often criticised with some developers advocating its removal from all high-level programming languages because it can lead to spaghetti code . 该命令经常受到一些开发人员的批评,他们主张将其从所有高级编程语言中删除,因为它可能导致意大利面条代码 This occurs when there are so many goto statements or similar jump statements that the code becomes difficult to read and maintain. 当有如此多的goto语句或类似的跳转语句使代码难以阅读和维护时,会发生这种情况。 However, there are programmers who point out that the goto statement, when used carefully, provides an elegant solution to some problems... 但是,有些程序员指出goto语句在仔细使用时可以为某些问题提供优雅的解决方案......

They left out this behaviour by design to avoid when it was not used by will but caused problems. 他们通过设计省略了这种行为,以避免它不被意志使用但引起问题。

It can be used only if there is no statement in the case part, like: 只有在案例部分中没有语句时才能使用它,例如:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}

They changed the switch statement (from C/Java/C++) behavior for c#. 他们更改了c#的switch语句(来自C / Java / C ++)行为。 I guess the reasoning was that people forgot about the fall through and errors were caused. 我猜测的原因是人们忘记了坠落而导致的错误。 One book I read said to use goto to simulate, but this doesn't sound like a good solution to me. 我读过的一本书说使用goto来模拟,但这听起来不是一个很好的解决方案。

A jump statement such as a break is required after each case block, including the last block whether it is a case statement or a default statement. 每个case块后都需要一个诸如break之类的跳转语句,包括最后一个块,无论是case语句还是default语句。 With one exception, (unlike the C++ switch statement), C# does not support an implicit fall through from one case label to another. 除了一个例外,(与C ++ switch语句不同),C#不支持从一个case标签到另一个case标签的隐式降级。 The one exception is if a case statement has no code. 一个例外是case语句没有代码。

-- C# switch() documentation - C#switch()文档

Just a quick note to add that the compiler for Xamarin actually got this wrong and it allows fallthrough. 简单地说,添加Xamarin的编译器实际上是错误的,它允许通过。 It has supposedly been fixed, but has not been released. 它应该是固定的,但尚未发布。 Discovered this in some code that actually was falling through and the compiler did not complain. 在一些实际上已经失败的代码中发现了这一点,并且编译器没有抱怨。

在每个case语句之后,即使它是默认情况,也需要breakgoto语句。

You can achieve fall through like c++ by the goto keyword. 您可以通过goto关键字实现类似c ++的功能。

EX: EX:

switch(num)
{
   case 1:
      goto case 3;
   case 2:
      goto case 3;
   case 3:
      //do something
      break;
   case 4:
      //do something else
      break;
   case default:
      break;
}

C# doesn't support fall through with switch/case statements. C#不支持使用switch / case语句。 Not sure why, but there's really no support for it. 不知道为什么,但实际上没有它的支持。 Linkage 连锁

switch (C# Reference) says 开关(C#参考)说

C# requires the end of switch sections, including the final one, C#需要切换部分的结尾,包括最后一部分,

So you also need to add a break; 所以你还需要添加break; to your default section, otherwise there will still will be a compiler error. 到你的default部分,否则仍然会有编译器错误。

You forgot to add the "break;" 你忘了添加“休息”; statement into case 3. In case 2 you wrote it into the if block. 声明到案例3.在案例2中,您将其写入if块。 Therefore try this: 因此试试这个:

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM