在C#中切换语句贯穿始终?

开关语句贯穿是我爱的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[]应该在函数之外声明:嗯,这只是一个例子。

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

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

为什么? 有没有办法得到这种行为没有三个if S?

( 我在别处提供的答案的复制/粘贴)

case中没有代码(见case 0 ),或者使用特殊的goto case (见case 1 )或goto default (参见case 2 )的forms,可以实现通过switch case的下降:

 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和1.5之前的Java中是不常见的错误来源。

解决方法是使用goto,例如

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

在我看来,开关/shell的总体devise有点不幸。 它与C的距离太近 – 在范围等方面可能会做出一些有益的改变。可以说,一个能够进行模式匹配的更智能的开关将会有所帮助,但是从切换到“检查一系列条件” – 在这一点上可能会要求一个不同的名字。

历史上,切换延迟是现代软件中的主要缺陷之一。 语言devise师决定强制在案件结束时跳过,除非您直接默认未经处理的下一个案例。

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

为了增加答案,我认为值得考虑与此相对的问题, 为什么C首先允许倒转?

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

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

因此,创build任何编程语言是如何最好地为这两个目标服务的一个平衡。 一方面,越容易变成计算机指令(不pipe是机器码,字节码如IL,还是在执行时解释指令),那么编译或解释过程将更有效率,更可靠输出紧凑。 在这个极端的情况下,这个目标导致了我们在汇编,IL甚至原始操作码方面的正式写作,因为最简单的汇编就是没有汇编的地方。

相反,语言expression程序员的意图越多,而不是为了达到这个目的而采取的手段,在编写和维护时程序越容易理解。

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

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

  1. 这只是自然而然发生的:如果你在一组指令中build立一个跳转表,并且其中一个较早的批处理指令不包含某种跳转或返回,那么执行自然会进入下一批。 允许转换是如果你转换switch话会发生什么 – 使用C转换成跳转表的机器代码。

  2. 在汇编中编写的编译器已经习惯了:在手工编写一个跳转表时,他们将不得不考虑一个给定的代码块是以一个返回,一个在表外跳转还是继续到下一个街区。 因此,编码人员在必要时添加明确的break对于编码人员来说也是“自然的”。

因此,当一个计算机语言的两个目标与生产的机器代码和源代码的expression性相关时,这是一个合理的尝试。

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

  1. 今天C中的编码器可能几乎没有assembly经验。 其他许多C语言的编码器更不可能(特别是Javascript!)。 任何“从assembly中习惯的人”的概念都不再相关。
  2. 优化方面的改进意味着switchif-else的可能性更高,因为它被认为是最有效的方法,或者转化为跳跃表方法的特别深奥的变体。 高级和低级方法之间的映射并不像以前那么强大。
  3. 经验表明,翻转往往是less数情况,而不是规范(Sun的编译器的一项研究发现,3%的switch块在同一个块上使用了除了多个标签之外的其他标签,并且认为使用这里的情况意味着这3%实际上远高于正常水平)。 所以,所研究的语言使得不寻常的东西比普通的东西更容易迎合。
  4. 经验表明,在意外事件发生的情况下,以及在维护代码的人员错过正确的翻身事件的情况下,翻倒往往是问题的根源。 后者是对与fall-through相关的错误的一个微妙的补充,因为即使你的代码完全没有错误,你的失败仍然可能会导致问题。

关于最后两点,请考虑下面的K&R当前版本的引用:

从一种情况到另一种情况不是很好,当程序被修改时容易崩溃。 除了用于单个计算的多个标签之外,应该谨慎使用fall-through,并对其进行评论。

作为一个很好的forms,尽pipe在逻辑上是不必要的,但在最后一个案例(在这里是默认的)之后要暂停一下。 有一天,当另一个案件被添加到最后,这一点的防御性编程将拯救你。

所以,从马的嘴里,在C中的穿透是有问题的。 总是logging带有注释的透视文件是一种很好的做法,这是一个普遍的原则的应用,我们应该logging一个不寻常的东西,因为这将是后面的代码检查和/或让你的代码看起来像当它实际上是正确的时候有一个新手的bug。

当你想到它时,代码就像这样:

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

在代码中添加了一些使fall-through变得明确的东西,这只是编译器不能检测到的东西(或者不能被检测到的东西)。

因此,在C#语言中,必须明确expression翻译的事实,不会对其他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#build立在已经正常的方式上,以处理一种情况,我们想要在一个switch碰到一个以上的代码块,并将其推广到覆盖掉落。 这也使得这两种情况都更加方便和自我logging,因为我们必须在C中添加一个新标签,但是可以在C#中使用该标签作为标签。 在C#中,我们可以去掉below_six标签,并使用goto case 5 ,这对我们正在做的事更加清楚。 (我们也必须添加break default ,我只是为了使上面的C代码显然不是C#代码)。

综上所述:

  1. C#不再像40年前的C代码那样直接与未优化的编译器输出相关联(这些日子也不是C),这使得贯通的启示之一无关紧要。
  2. C#与C保持兼容,不仅具有隐含的break ,熟悉相似语言的人员更容易学习语言,而且更容易移植。
  3. C#删除了一个可能的错误或错误代码的来源,这些代码在过去的四十年中已经被certificate是造成问题的原因。
  4. C#使得编译器可以强制执行现有的最佳实践(文档通过)。
  5. C#使不寻常的情况下,更明确的代码,通常情况下,与代码一个自动写入。
  6. C#使用相同的基于goto的方法从C中使用的不同case标签中find相同的块。它只是将其概括为其他一些情况。
  7. 通过允许case语句作为标签,C#使得基于goto的方法比在C中更方便,更清晰。

总而言之,这是一个非常合理的devise决定


*某些forms的BASIC将允许一个人做像GOTO (x AND 7) * 50 + 240这样虽然脆弱,因此特别有说服力的情况下禁止goto ,这样做有助于performance出更高的语言水平底层代码可以根据一个值进行算术运算,这在编译结果时更合理,而不是必须手动维护。 达夫设备的实现特别适合于等效的机器码或IL,因为每个指令块经常是相同的长度,而不需要添加nop填充物。

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

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

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

他们通过devise排除了这种行为,以避免不被意愿使用而造成问题。

只有在案件部分没有声明的情况下才能使用,如:

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

他们改变了c#的switch语句(来自C / Java / C ++)。 我想这是因为人们忘记了坠落事件和错误。 我读过的一本书说要用goto来模拟,但这听起来不像是一个很好的解决scheme。

在每个案件陈述后,即使是默认情况下也需要中断转让陈述。

每个大小写块之后都需要一个跳转语句(如中断),包括最后一个块是否是一个case语句或一个默认语句。 除了一个例外(与C ++ switch语句不同),C#不支持从一个案例标签到另一个案例标签的隐式下降。 一个例外是如果一个case语句没有代码。

– C#switch()文档

C#不支持switch / case语句。 不知道为什么,但真的没有支持。 连锁

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

只需要简单的说明一下,Xamarin的编译器实际上得到了这个错误,并且允许下载。 它应该是固定的,但还没有被释放。 在一些实际上正在经历的代码中发现了这一点,编译器也没有抱怨。

开关(C#参考)说

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

所以你也需要添加一个break; 到你的default部分,否则会出现编译错误。

你忘了添加“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"); }