397

switchSwitch 语句失败是我个人喜欢与if/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[]s 应该在函数之外声明:嗯,他们是,这只是一个例子。

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

控制不能从一个案例标签(“案例 3:”)转移到另一个案例标签
控制不能从一个案例标签(“案例 2:”)转移到另一个案例标签

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

4

13 回答 13

705

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

可以通过在 a (see ) 中没有代码或switch使用特殊的(see ) 或(see ) 形式来实现通过 -s:casecasecase 0goto casecase 1goto defaultcase 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;
}
于 2008-10-06T13:13:18.647 回答
47

“为什么”是为了避免意外跌倒,对此我很感激。这是 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 太近了 - 可以在范围等方面进行一些有用的更改。可以说,可以进行模式匹配等的更智能的开关会有所帮助,但这确实从开关变为“检查一系列条件” - 在这一点上,可能需要一个不同的名字。

于 2008-10-06T13:13:42.797 回答
28

为了补充这里的答案,我认为值得考虑与此相关的相反问题,即。为什么C首先允许失败?

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

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

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

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

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

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

  1. 无论如何它都会自然发生:如果您将跳转表构建到一组指令中,并且较早的一批指令中的一个不包含某种跳转或返回,那么执行将自然地进入下一批。switch如果您将-using C 转换为 jump-table-using 机器代码,则允许掉线是“刚刚发生”的事情。

  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# 中必须明确表示 on 的事实并不会对用其他 C 风格语言写得很好的人造成任何惩罚,因为他们已经在他们的失败中明确表示。†< /p>

最后,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# 建立在已经很正常的方式上来处理一种情况,即我们想要在 a 中点击多个代码块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# 使用与 C 中使用的相同goto的基于 - 的方法从不同的case标签中点击相同的块。它只是将其推广到其他一些情况。
  7. 通过允许语句充当标签, C# 使goto基于 - 的方法比在 C 中更方便、更清晰。case

总而言之,一个非常合理的设计决定


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

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

于 2013-12-09T12:15:29.340 回答
27

从历史上看,切换失败是现代软件中错误的主要来源之一。语言设计者决定强制在案例结束时跳转,除非您直接默认到下一个案例而不进行处理。

switch(value)
{
    case 1:// this is still legal
    case 2:
}
于 2008-10-06T13:06:10.533 回答
17

您可以“转到案例标签” http://www.blackwasp.co.uk/CSharpGoto.aspx

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

于 2008-10-06T13:08:37.143 回答
7

他们在设计上忽略了这种行为,以避免当它未被意志使用但引起问题时。

只有在 case 部分没有语句的情况下才能使用它,例如:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}
于 2008-10-06T13:04:34.343 回答
4

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

于 2008-10-06T13:03:03.270 回答
0

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

-- C# switch() 文档

于 2008-10-06T13:03:34.110 回答
0

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

于 2010-06-04T09:02:02.403 回答
0

您可以通过 goto 关键字实现像 c++ 一样的失败。

前任:

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;
}
于 2010-07-28T20:30:50.597 回答
0

只需快速说明,Xamarin 的编译器实际上出错了,它允许失败。据说已修复,但尚未发布。在一些实际上失败的代码中发现了这一点,编译器没有抱怨。

于 2014-02-14T22:19:05.647 回答
0

开关(C# 参考)说

C# 需要 switch 部分的结尾,包括最后一个,

所以你还需要在break;你的default部分中添加一个,否则仍然会出现编译器错误。

于 2014-10-07T09:33:53.613 回答
-12

您忘记添加“break;” 在案例 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");
}
于 2008-10-06T13:10:37.340 回答