切换声明在C#中落后?

时间:2008-10-06 13:00:16

标签: c# switch-statement

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的情况下获得这种行为?

14 个答案:

答案 0 :(得分:608)

(复制/粘贴answer I provided elsewhere

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

答案 1 :(得分:40)

“为什么”是为了避免意外跌落,我很感激。这是C和Java中不常见的bug漏洞。

解决方法是使用goto,例如

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

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

答案 2 :(得分:26)

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

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

答案 3 :(得分:20)

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

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

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

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

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

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

    1. 它无论如何都是自然发生的:如果你将一个跳转表构建成一组指令,并且其中一个早期批量指令不包含某种跳转或返回,那么执行就会很自然进入下一批。允许堕落是“刚刚发生的事情”#34;如果您将switch - 使用C转换为使用跳转表的机器代码。

    2. 在汇编中编写的编码器已经被用于等效:在汇编时手动编写跳转表时,他们必须考虑给定的代码块是否以返回结束,跳转到表,或者只是继续下一个块。因此,让编码人员在必要时添加明确的break是自然的"对于编码人员也是如此。

    3. 当时,这是一种合理的尝试,以平衡计算机语言的两个目标,因为它与生成的机器代码和源代码的表现力有关。

      四十年后,事情并不完全相同,原因如下:

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

          

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

             

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

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

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

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

        添加一些东西以使代码中的明确显式,它不是编译器可以检测到的(或者可以检测到它的缺失)。

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

        最后,这里使用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,这更清楚我们正在做什么。 (我们还必须为break添加default,我只是为了使上述C代码显然不是C#代码而遗漏。

        因此总结如下:

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


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

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

答案 4 :(得分:15)

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

  

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

答案 5 :(得分:8)

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

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

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

答案 6 :(得分:4)

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

答案 7 :(得分:0)

  

诸如休息之类的跳转声明是   在每个案件块之后需要,   包括最后一个块是否是   案例陈述或违约   声明。有一个例外,(不像   C ++ switch语句),C#没有   支持隐含的堕落   一个案例标签到另一个。唯一的那个   例外是如果案例陈述有   没有代码。

- C# switch() documentation

答案 8 :(得分:0)

每个案例陈述需要破解转到语句,即使它是默认情况。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

switch (C# Reference) says

  

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

因此,您还需要在break;部分添加default,否则仍会出现编译错误。

答案 12 :(得分:-1)

C#不支持使用switch / case语句。不知道为什么,但实际上没有它的支持。 Linkage

答案 13 :(得分:-12)

你忘记添加“休息”;声明到案例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");
}