与在C#中使用switch语句和if/else相比,优点/缺点是什么?除了您的代码外观,我无法想象有那么大的差异。
是否有任何原因导致最终的IL或相关的运行时性能发生根本性的差异?
相关:什么?更快,打开字符串还是其他类型?


评论

相关主题:stackoverflow.com/questions/335971/is-switch-case-always-wrong

从理论上讲,这个问题仅对大多数开发人员有意义,除非您经常发现自己迭代了10亿次。 (然后使用switch语句,从48秒缩短到43秒...)或用Donald Knuth的话说:“我们应该忘记效率低下,大约有97%的时间:过早的优化是万恶之源” zh.wikipedia.org/wiki/Program_optimization#When_to_optimize

我经常使用if / else而不是switch,因为switch的共享范围很有限。

#1 楼

在调试或兼容模式下,SWITCH语句仅生成与IF相同的程序集。在发行版中,它将被编译为跳转表(通过MSIL'switch'语句)-为O(1)。

C#(与许多其他语言不同)还允许打开字符串常量-和这有点不同。为任意长度的字符串构建跳转表显然不切实际,因此大多数情况下,此类开关将被编译为IF堆栈。

但是,如果条件数量大到足以应付开销,C#编译器将创建一个HashTable对象,用字符串常量填充该对象,并在该表上进行查找,然后跳转。哈希表查找并不是严格意义上的O(1),并且具有显着的常量成本,但是如果case标签的数量很大,则比与IF中的每个字符串常量进行比较要快得多。

总结一下,如果条件数大于5左右,则最好使用SWITCH而不是IF,否则请使用看起来更好的东西。

评论


您确定C#编译器会生成哈希表吗?在上面的注释讨论中,我对哈希表的观点是关于本机编译器,而不是C#编译器。 C#编译器用于生成哈希表的阈值是多少?

–斯科特·维斯涅夫斯基
08年12月28日在8:12

我想大概十点。 20为安全起见。在这两者之间,我的愤怒不是你,而是人们的支持和接受。

–ima
08年12月28日在8:15

一点实验表明计数<= 6:“如果”;计数> = 7:字典。这是与MS .NET 3.5 C#编译器一起使用的-当然,它可以在版本和供应商之间进行更改。

–乔恩·斯基特(Jon Skeet)
08年12月28日在8:22

嗨,我欠您一个道歉。很抱歉成为骨头。

–斯科特·维斯涅夫斯基
08年12月28日在8:39

作为后续操作,对于实际应用,大多数时候是否存在现实世界的差异?我发现C#中的switch语句很奇怪,它们的语法与其他语法完全不同,我发现它们使我的代码可读性降低,是否值得使用switch语句,还是应该只在其他情况下使用ifs编程,返回并更换它们,如果我遇到性能瓶颈?

– Jason Masters
19年1月26日在2:53

#2 楼

通常(考虑到所有语言和所有编译器),switch语句可以比if / else语句效率更高,因为编译器很容易从switch语句生成跳转表。在给定适当的约束的情况下,对于if / else语句可以执行相同的操作,但这要困难得多。 br />
使用大量的字符串,使用switch语句具有显着的性能优势,因为编译器将使用哈希表来实现跳转。

数目的字符串,两者之间的性能是相同的。

这是因为在这种情况下,C#编译器不会生成跳转表。相反,它会生成与IF / ELSE块等效的MSIL。

有一条“ switch语句” MSIL指令,当jitted时将使用跳转表来实现switch语句。但是,它仅适用于整数类型(此问题询问字符串)。

对于少量字符串,编译器生成IF / ELSE块比使用哈希表更有效。 。

当我最初注意到这一点时,我做一个假设,因为IF / ELSE块使用的字符串数量很少,所以编译器对大量的字符串进行了相同的转换。 >
这是错误的。 “ IMA”很友善地向我指出了这一点(嗯...他对此并不友善,但他是对的,而我是错的,这是重要的一部分)

我我还对MSIL中缺少“切换”指令做了一个粗心的假设(我认为,如果有一个切换原语,为什么他们不将其与哈希表一起使用,所以一定不能有一个切换原语。 ..)。这既是错误的,也是我的愚蠢之举。再次,“ IMA”向我指出了这一点。

我在这里进行了更新,因为它是评分最高的帖子,也是公认的答案。

但是,我之所以把它做成Community Wiki是因为我认为我不应该因为错误而提出REP。如果有机会,请对'ima'的帖子进行投票。

评论


MSIL中有一个switch原语,并且c#语句确实可以编译为类似C的查找。在某些情况下(目标平台,cl开关等),在编译过程中可能会将开关扩展为IF,但这仅是后备兼容性措施。

–ima
08年12月28日在7:04

好的...是的,有一个开关指令。对不起。 switch指令不适用于字符串类型。我更新了我的回复。感谢您的更正。

–斯科特·维斯涅夫斯基
08年12月28日在7:06

我所能做的就是为犯一个愚蠢的错误表示歉意。相信我,我对此感到愚蠢。认真地说,我认为这仍然是最好的答案。在本机编译器中,可以使用哈希表来实现跳转,因此这并不是一件非常错误的事情。我犯了一个错误。

–斯科特·维斯涅夫斯基
08年12月28日在7:25

ima,如果有错误,请指出。听起来Scott会很乐意更正该帖子。如果不是,则其他具有纠正答案能力的人也会这样做。这是这样的网站可以正常工作的唯一方法,并且通常情况下,它可以正常工作。或者带你的球回家:)

– jwalkerjr
08年12月28日在7:29

@斯科特:我鼓励您编辑第二和第三段以明确声明“用于字符串”。人们可能不会阅读底部的更新。

–乔恩·斯基特(Jon Skeet)
08年12月28日在8:19

#3 楼

选择switch的三个原因:



针对本机代码的编译器通常可以将switch语句编译为一个条件分支加上一个间接跳转,而if的序列则需要一系列条件分支。根据案例的密度,已经撰写了许多关于如何有效地编译案例陈述的学术论文。一些链接来自lcc编译器页面。 (Lcc具有更新颖的开关编译器之一。)
switch语句是互斥的替代选择,并且switch语法使该控制流对程序员来说比if-then-else语句嵌套更加透明。
在某些语言中,包括绝对是ML和Haskell,编译器会检查您是否遗漏了任何情况。我将此功能视为ML和Haskell的主要优势之一。我不知道C#是否可以做到这一点。

一个轶事:在他获得终身成就奖的一次演讲中,我听说Tony Hoare谈到了他职业生涯中所做的一切,他最引以为傲的有三个:


发明Quicksort
发明switch语句(Tony称之为case语句)
开始和结束他的职业生涯在行业中

我无法想象没有switch

#4 楼

编译器将在几乎没有差异的情况下将几乎所有内容优化到同一代码中(Knuth,有人吗?)。

不同之处在于,如果其他语句串在一起,则switch语句比15条更干净。

朋友不要让朋友堆叠if-else语句。

#5 楼

实际上,switch语句更有效。编译器会将其优化为一个查找表,使用if / else语句无法查找。不利的一面是switch语句不能与变量值一起使用。
不能:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}


它必须是
/>
switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}


评论


你有数字吗?我很好奇编译器在If / Else上如何优化swtich语句

–马修·奥斯本(Matthew M. Osborn)
08-12-28 at 0:21

是的,我相信switch语句始终会优化为O(1),其中if else语句将为O(n),其中n是if / else if语句中正确值的位置。

–kemiller2002
08-12-28 at 0:25

对于C#,这是不正确的,有关更多信息,请参见下面的我的帖子。

–斯科特·维斯涅夫斯基
08年12月28日在1:09

我不是很确定,但是我在发誓的书中找不到信息。确定要对MSIL代码进行优化吗?除非您进行优化编译,否则它不会创建跳转表。

–kemiller2002
08-12-28 at 3:54

我在调试和零售模式下都进行了编译,在两种情况下,它都会生成if / else块。您确定所看的书是关于C#的吗?这本书可能是编译器书籍,也可能是有关C或C ++的书籍。

–斯科特·维斯涅夫斯基
08年12月28日在4:00

#6 楼

我没有看到其他人提出(显而易见的)观点,即switch语句的假定效率优势取决于各种情况的可能性大致相同。如果一个(或几个)值的可能性更大,则通过确保首先检查最常见的情况,if-then-else阶梯可以更快得多:

示例:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}


vs

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}


如果x在90%的时间内为零,则“如果-else”代码的速度可以是基于开关的代码的两倍。即使编译器将“开关”转换为某种聪明的表驱动的goto,它仍然不如简单地检查零那么快。

评论


没有过早的优化!通常,如果您有不止几种情况并且它们与switch兼容,那么switch语句会更好(可读性更高,有时更快)。如果您知道一种情况的可能性更大,则可以将其拉出,以形成一个if-else-switch构造,并且如果它测量得更快,则将其保留下来。(如果需要,请重复。)IMO仍然是可读的。如果开关退化并且变得太小,则正则表达式替换将完成将其转换为else if链的大部分工作。

–没人
2012年7月23日在20:42

最初的问题(三年前!)只是询问if / else和switch之间的优缺点。这是一个例子。我个人已经看到这种优化对例程的运行时间有很大的影响。

–马克·贝西(Mark Bessey)
2012年7月23日在23:31

#7 楼

通常看起来会更好-即更容易理解正在发生的事情。考虑到性能优势最多将是极小的,代码视图是最重要的区别。

因此,如果if / else看起来更好,请使用它,否则请使用switch语句。 br />

#8 楼

副标题,但我经常担心(并且经常看到)if / elseswitch语句在太多情况下变得太大。这些通常会损害可维护性。

常见的罪魁祸首包括:


在多个if语句中进行过多的操作
比人类分析更多的case语句
if评估中的条件太多,无法知道要寻找的内容

要解决的问题:


提取到方法重构。
使用带有方法指针的Dictionary代替大小写,或使用IoC来增加可配置性。方法工厂也可能会有所帮助。
将条件提取为自己的方法


评论


真实的示例代码(1,2,3个步骤)?

– Kiquenet
12月10日21:58

#9 楼

按照此链接,使用switch和if语句进行的IF与Switch进行迭代测试的比较类似于1,000,000,000次迭代,其中Switch Statement = 43.0s和If Statement = 48.0s花费的时间

每秒20833333次迭代,因此,我们是否真的需要更加关注,

PS:只需了解少量条件下的性能差异即可。

#10 楼

如果您仅使用if或else语句,则基本解决方案使用的是comparsion?操作员

(value == value1) ? (type1)do this : (type1)or do this;


您可以在开关中执行或例程

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}


#11 楼

这实际上并不能回答您的问题,但是鉴于编译版本之间的差异很小,我敦促您以最能描述您意图的方式编写代码。

如果您打算基于一个程序的值来分支程序,那么编译器不仅可以更好地完成您期望的操作,还可以使其他人更轻松地维护您的代码。变量/属性,则switch语句最能表示该意图。

如果您的意图是基于不同的变量/属性/条件分支程序,则if / else if链最能表示该意图。

我会认为,对于人们忘记了break命令的情况,科迪是正确的,但是几乎经常我看到人们在错误地使用{}的情况下执行复杂的代码块,因此条件语句中的行应该是正确的不是。这是我始终在if语句中包含{}的原因之一,即使其中只有一行。不仅更容易阅读,而且如果需要在条件中添加另一行,我也不会忘记添加它。

#12 楼

利息问题。几个星期前在工作中出现了这个问题,我们通过编写示例片段并在.NET Reflector中进行查看找到了答案(reflector非常棒!我喜欢它)。

这是我们发现的内容:
除字符串以外的其他任何有效的switch语句都将作为switch语句编译到IL中。但是,如果它是字符串,则在IL中将其重写为if / else if / else。因此,在我们的案例中,我们想知道switch语句如何比较字符串(例如,区分大小写等),并且反射器很快为我们提供了答案。这很有用。

如果要对字符串进行区分大小写的比较,则可以使用switch语句,因为它比执行String更快。在if / else中比较。 (进行一些实际的性能测试(编辑:阅读“什么更快,在字符串上打开或在类型上打开elseif”?))。但是,如果您想进行不区分大小写的操作,则最好使用if / else,因为结果代码不太漂亮。

switch (myString.ToLower())
{
  // not a good solution
}


最好的经验法则是,如果有意义(严重),则使用switch语句,例如:


it提高代码的可读性
您正在比较值的范围(浮点型,整型)或枚举

如果需要操纵值以馈入switch语句(创建一个临时值)要切换的变量),则可能应该使用if / else控制语句。

更新:

实际上将字符串转换为大写实际上更好(例如ToUpper() ),因为显然已经有了进一步的优化,与ToLower()相比,即时编译器可以做到。这是一个微优化,但是在紧密的循环中可能很有用。


注意事项:

要提高switch语句的可读性,请尝试以下操作:


最有可能出现的分支,即最易访问的分支
,如果它们都有可能发生,请按字母顺序列出它们,以便于查找。
永远不要在最后剩下的条件下使用默认的包罗万象,这是很懒惰的,并且会在代码生命的后期引起问题。
使用默认的包罗万象来断言未知状况,即使极不可能发生这种状况。这才是断言所擅长的。


评论


在许多情况下,使用ToLower()是正确的解决方案,尤其是在有很多情况并且生成哈希表的情况下。

–布莱布莱德
09年1月16日在3:48

“如果需要操纵该值以将其馈送到switch语句(创建要进行切换的临时变量),则可能应该使用if / else控制语句。” -好的建议,谢谢。

–鬼S
09年12月3日在15:24

#13 楼

switch语句肯定比if if更快。 BlackWasp已提供了速度测试

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

-将其检出

但是在很大程度上取决于您要考虑的可能性,但是我会尽可能尝试使用switch语句。

#14 楼

我认为不仅是C#,还包括所有基于C的语言:由于开关仅限于常量,因此可以使用“跳转表”生成非常有效的代码。 C的情况确实是旧的FORTRAN计算得出的GOTO,但是C#的情况仍在针对常量进行测试。

优化器将无法生成相同的代码。考虑例如

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}


,因为它们是复合布尔值,所以生成的代码必须计算值并短路。现在考虑等效项

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}


由于您隐式告诉编译器,因此可以将其编译为

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x


它不需要计算OR和相等性测试。

评论


只要实现了优化,就没有理由好的优化器无法处理第一个代码。 “编译器无法优化”仅取决于语义差异,只有人类可以和解(即,如果调用了f(),则不知道f()总是返回0或1)。

–布莱布莱德
09年1月16日,下午3:45

#15 楼

我的CS教授建议不要切换语句,因为通常人们会忘记休息或错误地使用它。我记不清他说的是什么,但顺便看一下一些精简的代码库,这些库显示了switch语句的示例(几年前),其中也有很多错误。

评论


在C#中并不是真正的问题。请参阅:stackoverflow.com/questions/174155 / ... ...,另请阅读stackoverflow.com/questions/188461 / ...,以获取有关为何生活在恐惧中可能不是最佳政策的讨论...

–Shog9
08-12-28 at 1:04

#16 楼

我刚刚注意到的是,您可以组合if / else和switch语句!需要检查前提条件时非常有用。

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}


评论


我知道这很老了,但从技术上讲,您并不是在“组合”任何东西。任何时候如果您使用不带花括号的“ else”,将执行下一条语句。该语句可以是单行语句,通常在下一行缩进显示,也可以是复合语句,例如if,switch,using,lock等情况。换句话说,您可以使用“ else if”,“话虽如此,我确实喜欢它的外观,几乎看起来是有意的。 (免责声明:我还没有尝试过所有这些方法,所以我可能错了!)

–纳尔逊·罗瑟梅尔(Nelson Rothermel)
2010-2-22在20:56



尼尔森,你是100%正确的。发布此答案后,我想出了为什么会这样。

–连面
2010-2-22在21:30

#17 楼

我认为切换比情况快得多
,看看是否有类似程序:
编写一个程序以输入任意数字(1 – 99之间)并检查它在哪个插槽中a)1 – 9然后插入第一个b)11–19,然后插入第二个c)21-29,然后插入第三,依此类推,直到89-99
然后,如果您必须进行很多条件操作,但必须切换儿子类型,然后键入

开关(否/ 10)
情况0 = 1-9,情况1 = 11-19等

如此简单
还有更多这样的例子!

#18 楼

switch语句基本上是对相等性的比较。键盘事件相对于switch语句具有很大的优势,因为它易于编写和读取代码,因此if elseif语句会丢失{括号}也可能会造成麻烦。

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}


如果if elseif语句对更多内容有用,那么一个解决方案if(theAmountOfApples大于5,&& theAmountOfApples小于10)保存您的苹果
否则if(theAmountOfApples大于10 || theAmountOfApples == 100)卖出您的苹果。我不写c#或c ++,但是我在学习Java之前就已经学过了,它们是紧密的语言。

#19 楼

switch语句的一个缺点可能是缺少多个条件。您可以为if(else)设置多个条件,但不能在开关中具有不同条件的多个case语句。

switch语句不适合简单布尔表达式/表达式范围之外的逻辑运算。对于那些布尔方程式/表达式,它非常适合,但不适用于其他逻辑运算。

If语句中的可用逻辑具有更大的自由度,但是如果If语句变得笨拙或处理不善。

根据您所面对的情况,两者都有。

#20 楼

我的2美分就可以了。在大多数情况下,如果性能不是一个标准,那么它更多地是关于代码的可读性。如果if / else语句的数量比使用switch语句的数量多,则更好。