繁体   English   中英

C# 中的 Switch 语句失败?

[英]Switch statement fallthrough in C#?

Switch 语句失败是我个人喜欢switchif/else if结构的主要原因之一。 这里有一个例子:

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;
}

聪明的人害怕,因为string[]应该在函数之外声明:嗯,他们是,这只是一个例子。

编译器失败并出现以下错误:

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

为什么? 有没有办法在没有三个if的情况下获得这种行为?

(复制/粘贴我在其他地方提供答案

通过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;
}

“为什么”是为了避免意外跌倒,对此我感激不尽。 这是C和Java中不常见的错误来源。

解决方法是使用goto,例如

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

在我看来,开关/外壳的总体设计有点不幸。 它离C太近 - 有一些有用的变化可以在范围等方面进行。可以说,一个更聪明的开关可以进行模式匹配等会有所帮助,但这确实从开关变为“检查一系列条件” - 此时可能会要求使用其他名称。

交换机漏洞历史上是现代软件中错误的主要来源之一。 语言设计者决定强制要求在案例结束时跳转,除非您在没有处理的情况下直接默认为下一个案例。

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

为了在这里添加答案,我认为值得考虑与此相关的相反问题,即。 为什么C首先允许掉线?

任何编程语言当然都有两个目标:

  1. 向计算机提供说明。
  2. 留下程序员的意图记录。

因此,任何编程语言的创建都是如何最好地服务于这两个目标之间的平衡。 一方面,变得更容易变成计算机指令(无论是机器代码,字节码如IL,还是指令在执行时被解释),那么编译或解释过程将更加高效,可靠和紧凑的输出。 尽管如此,这个目标导致我们只是在汇编,IL或甚至原始操作码中编写,因为最简单的编译是根本没有编译的地方。

相反,语言表达程序员的意图越多,而不是为此目的采取的手段,在编写和维护期间程序就越容易理解。

现在, switch总是可以通过将其转换为等效的if-else块或类似链来编译,但它被设计为允许编译成特定的公共汇编模式,其中一个取值,计算它的偏移量(无论是查找由值的完美哈希索引的表,或通过值*的实际算术查找。 在这一点上值得注意的是,今天,C#编译有时会switch为等效的if-else ,有时会使用基于散列的跳转方法(同样使用C,C ++和其他具有可比语法的语言)。

在这种情况下,有两个很好的理由允许掉期:

  1. 它无论如何都是自然发生的:如果你将一个跳转表构建成一组指令,并且其中一个早期批量指令不包含某种跳转或返回,那么执行将自然地进入下一批。 如果你将switch成使用机器码的跳转表,那么允许掉线就是“刚刚发生”。

  2. 在汇编中编写的编码器已经习惯了等价物:当在汇编中手动编写跳转表时,他们必须考虑给定的代码块是以返回结束,跳出表格还是继续到下一个街区。 因此,让编码器在必要时添加明确的break对于编码器来说也是“自然的”。

因此,在计算机语言的两个目标之间进行平衡是合理的尝试,因为它与生成的机器代码和源代码的表达性有关。

四十年后,情况并不完全相同,原因有以下几点:

  1. 今天C中的编码员可能很少或根本没有装配经验。 许多其他C风格语言的编码器甚至不太可能(尤其是Javascript!)。 任何“人们习惯于装配”的概念都不再相关。
  2. 优化的改进意味着switch的可能性要么变成if-else因为它被认为是最有效的方法,或者转变为跳转表方法的特别深奥的变体更高。 较高级别和较低级别方法之间的映射不像以前那样强大。
  3. 经验表明,跌倒往往是少数情况而不是常态(对Sun的编译器的研究发现,3%的switch块在同一块上使用了除了多个标签之外的掉落,并且认为使用 - 这里的例子意味着这3%实际上远高于正常水平)。 因此,所研究的语言使得不寻常的事情比普通的更容易照顾。
  4. 经验表明,在意外完成的情况下,以及在维护代码的人错过正确的漏报的情况下,跌倒往往是问题的根源。 后者是与掉落相关的错误的一个微妙的补充,因为即使您的代码完全没有错误,您的堕落仍然可能导致问题。

与最后两点相关,请考虑当前版本的K&R中的以下引用:

从一个案例落到另一个案例并不健全,在修改程序时容易崩溃。 除了单个计算的多个标签之外,应谨慎使用漏洞并进行评论。

作为一个好的形式,在最后一个案例(默认在这里)之后休息,即使它在逻辑上是不必要的。 有一天,当最后添加另一个案例时,这一点防御性编程将为您节省开支。

因此,从马的口中,C中的跌落是有问题的。 总是记录带有注释的漏洞是一种良好的做法,这是应用一般原则的应用,应该记录一个人做一些不寻常的事情,因为这将是后来检查代码和/或使你的代码看起来像什么的当它实际上是正确的时,它有一个新手的错误。

当你考虑它时,代码如下:

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

添加的东西,使落空在代码中明确的,它只是没有东西可以被检测(或者其缺乏可检测)的编译器。

因此,事实上必须明确表示C#中的堕落并不会给那些在其他C风格的语言中写得很好的人增加任何惩罚,因为他们已经在他们的堕落中明确表示。†

最后, 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();
}

在这种情况下,我们希望将一个块包含在为除前一个块之外的值执行的代码中执行的代码中,然后我们必须使用goto (当然,有一些方法和方法可以通过不同的条件避免这种情况,但对于与这个问题相关的所有事情都是如此)。 因此,C#建立在已经正常的方式来处理我们想要在switch击中多个代码块的一种情况,并且只是将其概括为覆盖掉落。 它还使两个案例更方便和自我记录,因为我们必须在C中添加新标签,但可以将case用作C#中的标签。 在C#中,我们可以删除below_six标签并使用goto case 5 ,它更清楚我们正在做什么。 (我们还必须为default添加break ,我只是为了使上面的C代码显然不是C#代码而遗漏了)。

因此总结如下:

  1. C#不再像40年前的C代码那样直接与未经优化的编译器输出相关(这些天也不是C),这使得堕落的灵感之一无关紧要。
  2. C#与C语言保持兼容,不仅具有隐式break ,因为熟悉类似语言的人更容易学习语言,并且更容易移植。
  3. C#删除了一个可能的错误来源或被误解的代码,这些代码在过去四十年中已被充分记录为导致问题。
  4. C#通过编译器强制执行C(文档落实)现有的最佳实践。
  5. C#使得不常见的情况是具有更明确代码的情况,通常情况下代码之一只是自动写入。
  6. C#使用相同的基于goto的方法从C中使用的不同case标签中命中相同的块。它只是将其推广到其他一些情况。
  7. 通过允许case语句作为标签,C#使得基于goto的方法比在C中更方便,更清晰。

总而言之,这是一个非常合理的设计决策


*某些形式的BASIC可以让人们做GOTO (x AND 7) * 50 + 240 ,虽然脆弱,因此禁止goto特别有说服力的情况,确实有助于显示更高语言的等效方式较低级别的代码可以根据对值的算术进行跳转,这在编译结果时更合理,而不是必须手动维护。 Duff设备的实现尤其适用于等效的机器代码或IL,因为每个指令块通常具有相同的长度而无需添加nop填充器。

†Duff的设备再次出现在这里,作为一个合理的例外。 事实上,即使没有对这种效果的明确评论,使用这种和相似的模式重复操作也可以使得使用跌倒相对清晰。

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

goto语句是一个简单的命令,它无条件地将程序的控制权转移到另一个语句。 该命令经常受到一些开发人员的批评,他们主张将其从所有高级编程语言中删除,因为它可能导致意大利面条代码 当有如此多的goto语句或类似的跳转语句使代码难以阅读和维护时,会发生这种情况。 但是,有些程序员指出goto语句在仔细使用时可以为某些问题提供优雅的解决方案......

他们通过设计省略了这种行为,以避免它不被意志使用但引起问题。

只有在案例部分中没有语句时才能使用它,例如:

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

他们更改了c#的switch语句(来自C / Java / C ++)行为。 我猜测的原因是人们忘记了坠落而导致的错误。 我读过的一本书说使用goto来模拟,但这听起来不是一个很好的解决方案。

每个case块后都需要一个诸如break之类的跳转语句,包括最后一个块,无论是case语句还是default语句。 除了一个例外,(与C ++ switch语句不同),C#不支持从一个case标签到另一个case标签的隐式降级。 一个例外是case语句没有代码。

- C#switch()文档

简单地说,添加Xamarin的编译器实际上是错误的,它允许通过。 它应该是固定的,但尚未发布。 在一些实际上已经失败的代码中发现了这一点,并且编译器没有抱怨。

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

您可以通过goto关键字实现类似c ++的功能。

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#不支持使用switch / case语句。 不知道为什么,但实际上没有它的支持。 连锁

开关(C#参考)说

C#需要切换部分的结尾,包括最后一部分,

所以你还需要添加break; 到你的default部分,否则仍然会有编译器错误。

你忘了添加“休息”; 声明到案例3.在案例2中,您将其写入if块。 因此试试这个:

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