在C#中使用if / else和switch-case有什么明显的区别吗?


219

switchif/elseC#中的语句相比,使用语句有什么好处/缺点?除了您的代码外观之外,我无法想象有那么大的区别。

有什么原因导致最终的IL或相关的运行时性能完全不同?

相关:什么更快,打开字符串或其他类型?



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

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

Answers:


340

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

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

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

综上所述,如果条件数大于5左右,则应优先选择SWITCH而不是IF,否则请使用看起来更好的东西。


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

8
我想大概十点。20为安全起见。顺便说一句,我的愤怒不是你,而是人们的支持和接受。
ima

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

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

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

54

通常(考虑到所有语言和所有编译器),switch语句可以比if / else语句更有效,因为编译器很容易从switch语句生成跳转表。给定适当的约束,对于if / else语句可以执行相同的操作,但这要困难得多。

对于C#,也是如此,但由于其他原因。

对于大量的字符串,使用switch语句具有显着的性能优势,因为编译器将使用哈希表来实现跳转。

使用少量的字符串,两者之间的性能是相同的。

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

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

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

当我最初注意到这一点时,我就做出了假设,因为IF / ELSE块使用的字符串数量很少,所以编译器对大量的字符串进行了相同的转换。

这是错误的。“ IMA”很友善地向我指出了这一点(嗯……他对此并不友善,但他是对的,而我是错的,这很重要)

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

我在此进行了更新,因为它是评分最高的帖子,也是被接受的答案。

但是,我把它做成Community Wiki是因为我认为我不应该因为错误而提出REP。如果有机会,请投票给'ima'职位。


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

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

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

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

4
@ima如果您认为答案在客观上是错误的,则将其编辑为正确的。这就是每个人都可以编辑答案的原因。
Miles Rout 2015年

18

选择以下三个原因switch

  • 面向本机代码的编译器通常可以将switch语句编译为一个条件分支加上一个间接跳转,ifs 序列则需要一系列条件分支。根据案例的密度,已经撰写了许多关于如何有效地编译案例陈述的学习论文。有些链接来自lcc编译器页面。(Lcc具有更创新的开关编译器之一。)

  • switch语句是相互排斥选择之间选择,并且switch语法使该控制流对程序员而言比if-then-else语句嵌套更为透明

  • 在某些语言(包括ML和Haskell)中,编译器将检查是否遗漏了任何情况。我将此功能视为ML和Haskell的主要优势之一。我不知道C#是否可以做到这一点。

轶事:在他获得终生成就奖的演讲中,我听到托尼·霍尔说他在职业生涯中所做的所有事情中,他最引以为傲的有三个:

  • 发明Quicksort
  • 发明switch语句(Tony称为该case语句)
  • 开始和结束他的工业生涯

无法想象没有switch


16

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

区别在于,如果其他语句串在一起,则switch语句比15条更干净。

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


13
“朋友不要让朋友堆叠if-else语句。” 您应该制作一个炸弹小家伙:)
马修·奥斯本

14

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

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

它一定要是

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

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

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

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

我并不完全确定这一点,但是我发誓我在那本书中找不到信息。您确定您没有在没有优化的情况下查看MSIL代码。除非您进行优化编译,否则它不会创建跳转表。
kemiller2002

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

14

我没有看到其他人提出(显而易见的?)观点,即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
}

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

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


3
没有过早的优化!通常,如果您有不止几种情况且它们是switch兼容的,则该switch语句会更好(可读性更高,有时更快)。 如果你知道一个情况下更可能多的,你可以拉说出来,形成一个if- else- switch结构和,如果它是可测量的速度更快,你离开,在(重复,如果需要的话。)IMO这仍然相当可读。如果switch退化并变得太小,则正则表达式替换将完成将其转换为else if-chain的大部分工作。
没人

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

7

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

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


4

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

常见的罪魁祸首包括:

  1. 在多个if语句中做太多
  2. 案例分析多于人工分析
  3. if评估中的条件太多,无法知道要寻找的内容

修理:

  1. 提取到方法重构。
  2. 使用带有方法指针的Dictionary(而不是大小写),或使用IoC来增加可配置性。方法工厂也可能会有所帮助。
  3. 提取条件到自己的方法

4

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

实际上是每秒20833333次迭代,因此,我们是否真的需要集中精力,

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


那对我很重要。
格雷格·古姆

3

如果您仅使用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;
}

2

这实际上并不能回答您的问题,但是鉴于编译版本之间的差异很小,我敦促您以最能描述您意图的方式编写代码。编译器不仅可以更好地完成您期望的操作,还可以使其他人更轻松地维护您的代码。

如果您打算基于一个变量/属性的值来分支程序,则switch语句最能代表该意图。

如果您打算基于不同的变量/属性/条件来分支程序,那么if / else if链最能代表该意图。

对于人们忘记了break命令,我会认为cody是对的,但是几乎我经常看到人们在if块弄错{}的情况下执行复杂的操作,因此条件语句中的行不是。这是即使在其中一行中也总是在 {}语句中包含{} 的原因之一。不仅更易于阅读,而且如果需要在条件中添加另一行,我也不会忘记添加它。


2

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

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

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

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

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

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

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

更新:

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


一点注意事项:

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

  • 将最可能的分支放在第一位,即访问最多的分支
  • 如果它们都可能发生,请按字母顺序列出它们,以便于查找。
  • 切勿在最后剩下的情况下使用默认的包罗万象,这是很懒惰的,并且会在以后的代码中引起问题。
  • 使用默认的包罗万象来断言未知情况,即使极不可能发生这种情况。这就是断言有好处的。

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

“如果您需要操纵该值以将其输入到switch语句中(创建一个临时变量进行切换),那么您可能应该使用if / else控制语句。” -好的建议,谢谢。
偷偷摸摸的


1

我认为,不仅是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)。
Blaisorblade

0

我的CS教授建议不要切换语句,因为很多时候人们会忘记休息或错误地使用它。我记不清他说的是什么,但是根据一些具有开创性的代码库(显示年前的switch语句示例)来看,其中也有很多错误。


在C#中并不是真正的问题。参见:stackoverflow.com/questions/174155/… ...,另请阅读stackoverflow.com/questions/188461/…,以了解为什么生活在恐惧中可能不是最好的政策……
Shog9

0

我刚刚注意到的是,您可以组合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;
}

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

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

0

我认为Switch的速度要比条件查看是否有以下程序快:

编写一个程序以输入任何数字(介于1 – 99之间),并检查它在哪个插槽中:a)1 – 9然后插槽1)b)11 – 19然后插槽2 c)21-29然后插槽3以此类推,直到89- 99

然后打开,如果您必须做出许多条件,但您必须输入儿子切换大小写

开关(否/ 10)

并以情况0 = 1-9,情况1 = 11-19依此类推

会很容易

还有更多这样的例子!


0

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

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

if elseif语句最适合于一个解决方案,如果(theAmountOfApples大于5并且&theAmountOfApples小于10)保存您的苹果,否则,如果(theAmountOfApples大于10 || theAmountOfApples == 100)出售您的苹果。我不写c#或c ++,但是我在学习Java之前就已经学过了,它们是紧密的语言。


0

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

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

If语句中的逻辑具有更大的自由度,但是如果If语句变得笨拙或处理不当,则可读性会受到影响。

两者的位置取决于您所面对的环境。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.