ChatGPT解决这个技术问题 Extra ChatGPT

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

在 C# 中使用 switch 语句与 if/else 相比有什么好处/缺点。除了代码的外观之外,我无法想象会有那么大的不同。

产生的 IL 或相关的运行时性能有什么根本不同的原因吗?

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

这个问题对大多数开发人员来说只是理论上的有趣,除非你经常发现自己迭代了 10 亿次。 (然后使用 switch 语句并从 48 秒变为 43 秒......)或者用 Donald Knuth 的话来说:“我们应该忘记小的效率,说大约 97% 的时间:过早优化是万恶之源” en.wikipedia.org/wiki/Program_optimization#When_to_optimize
我经常使用 if/else 而不是 switch,因为 switch 的共享范围很奇怪。

S
Sнаđошƒаӽ

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

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

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

综上所述,如果条件数超过5个左右,则优先选择SWITCH而不是IF,否则使用看起来更好的任何东西。


您确定 C# 编译器会生成哈希表吗?我在上面的评论讨论中关于哈希表的观点是关于本机编译器,而不是 C# 编译器。 C# 编译器使用什么阈值来生成哈希表?
我想大约十个。 20为了安全起见。顺便说一句,我的愤怒不是你,而是人们支持和接受。
一些实验表明 count <= 6: "if";计数 >= 7:字典。这与 MS .NET 3.5 C# 编译器有关——当然,它可能会在版本和供应商之间发生变化。
作为后续,对于实际应用,大多数时候是否有任何现实世界的差异?我发现 C# 中的 switch 语句很奇怪,它们的语法与其他任何东西都不一样,而且我发现它们使我的代码可读性降低,是否值得费心使用 switch 语句,或者我应该只使用 else ifs 进行编程并且只来如果我遇到性能瓶颈,请返回并更换它们?
我对我们应用程序中使用的 18 个字符串(3-35 个字符长)进行了特定测试。 IF 速度提高了 18%。
7
7 revs

通常(考虑到所有语言和所有编译器)switch 语句有时会比 if / else 语句更有效,因为编译器很容易从 switch 语句生成跳转表。给定适当的约束条件,可以对 if / else 语句做同样的事情,但这要困难得多。

在 C# 的情况下,这也是正确的,但出于其他原因。

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

用少量的琴弦,两者的性能是一样的。

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

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

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

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

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

我还对 MSIL 中缺少“switch”指令做出了一个愚蠢的假设(我想,如果有 switch 原语,他们为什么不将它与哈希表一起使用,所以一定没有 switch 原语。 ...)。这既是错误的,对我来说也是非常愚蠢的。 'IMA' 再次向我指出了这一点。

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

但是,我将其设为 Community Wiki,因为我认为我不应该因为错误而获得 REP。如果您有机会,请为“ima”的帖子投票。


MSIL 中有一个 switch 原语,并且 c# 语句确实编译成通常类似于 C 的查找。在某些情况下(目标平台、cl 开关等)开关可能会在编译期间扩展为 IF,但这只是一种回退兼容性措施。
好的...是的,有一个开关指令。对不起。 switch 指令不适用于字符串类型。我更新了我的回复。感谢您的指正。
我能做的就是为犯了一个愚蠢的错误道歉。相信我,我对此感到很愚蠢。不过说真的,我认为这仍然是最好的答案。在本机编译器中,可以使用哈希表来实现跳转,所以这并不是什么可怕的错误。我犯了一个错误。
ima,如果有错误,指出它们。听起来斯科特会很乐意纠正这个帖子。如果没有,其他已经能够纠正答案的人也会这样做。这是像这样的网站可以工作的唯一方式,而且总的来说,它似乎在工作。或者带上你的球回家:)
@Scott:我鼓励您编辑第二段和第三段以明确说明“用于字符串”。人们很可能不会阅读底部的更新。
佚名

编译器会将几乎所有内容优化为相同的代码,但差异很小(Knuth,有人吗?)。

不同之处在于 switch 语句比 15 个 if else 语句串在一起更清晰。

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


N
Norman Ramsey

选择 switch 的三个原因:

针对本机代码的编译器通常可以将 switch 语句编译成一个条件分支加上一个间接跳转,而 ifs 序列需要一系列条件分支。根据案例的密度,已经写了很多关于如何有效地编译案例陈述的学术论文。有些是从 lcc 编译器页面链接的。 (Lcc 拥有用于开关的更具创新性的编译器之一。)

switch 语句是相互排斥的选项中的一种选择,switch 语法使这个控制流对程序员来说比 if-then-else 语句的嵌套更透明。

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

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

发明快速排序

发明 switch 语句(托尼称之为 case 语句)

开始和结束他在工业界的职业生涯

无法想象没有switch的生活。


k
kemiller2002

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

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

它一定要是

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

你有什么数字吗?我很好奇编译器如何通过 If/Else 优化 swtich 语句
是的,我相信 switch 语句总是优化到 O(1),其中 if else 语句将为 O(n),其中 n 是 if/else if 语句中正确值的位置。
在 C# 的情况下,这是不正确的,有关更多信息,请参阅下面的我的帖子。
我不完全确定这一点,但我在我发誓我找到它的书中找不到信息。你确定你没有在没有优化的情况下查看 MSIL 代码。除非您使用优化进行编译,否则它不会创建跳转表。
我在调试和零售模式下编译,在这两种情况下它都会生成 if / else 块。您确定您正在看的书是在谈论 C# 吗?这本书可能是一本编译器的书或一本关于 C 或 C++ 的书
M
Mark Bessey

我没有看到其他人提出(显而易见的?)观点,即 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,它仍然不会像简单地检查零一样快。


没有过早的优化!一般来说,如果您有多个案例并且它们与 switch 兼容,则 switch 语句会更好(更易读,有时更快)。 如果您知道一种情况更有可能发生,您可以将其提取出来以形成一个 if-else-switch 构造,并且如果它明显更快 ,你把它留在里面。(如果需要,重复。)IMO 仍然可以合理地阅读。如果 switch 退化并且变得太小,则正则表达式替换将完成将其转换为 else if 链的大部分工作。
最初的问题(三年前!)只是询问 if/else 和 switch 之间的优缺点。这是一个例子。我个人已经看到这种优化对例程的运行时间产生了重大影响。
g
gbjbaanb

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

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


S
Shog9

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

常见的罪魁祸首包括:

在多个 if 语句中做太多事情 比人工分析更多的 case 语句 if 评估中的条件太多,无法知道要查找的内容

修理:

提取到方法重构。使用带有方法指针的 Dictionary 而不是 case,或者使用 IoC 来增加可配置性。方法工厂也很有帮助。提取条件到自己的方法


关于婴儿步的真实示例代码(1、2、3 步)?
s
sth

如果您只是使用 if 或 else 语句,则基本解决方案正在使用比较?操作员

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

您可以在 switch 中执行 or 例程

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

B
Bretfort

根据此链接,IF vs Switch 使用 switch 和 if 语句进行迭代测试的比较类似于 1,000,000,000 次迭代,Switch 语句花费的时间=43.0s & by If 语句 = 48.0s

这实际上是每秒 20833333 次迭代,所以,我们是否真的需要更多地关注,

PS:只是为了知道一小部分条件的性能差异。


d
dj_segfault

这实际上并不能回答您的问题,但鉴于编译版本之间几乎没有区别,我会敦促您以最能描述您的意图的方式编写代码。编译器不仅有更好的机会按照您的预期进行操作,而且还可以让其他人更容易维护您的代码。

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

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

我承认 cody 关于人们忘记了 break 命令的说法是正确的,但是我几乎同样经常看到人们在执行复杂的 if 块时会弄错 {},所以应该在条件语句中的行不是。这是我总是在 if 语句中包含 { } 的原因之一,即使其中只有一行。不仅更容易阅读,而且如果我需要在条件中添加另一行,我不能忘记添加它。


C
Community

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

这就是我们所发现的:除字符串之外的任何内容的有效 switch 语句都被编译为 IL 作为 switch 语句。然而,如果它是一个字符串,它在 IL 中被重写为 if/else if/else。因此,在我们的案例中,我们想知道 switch 语句如何比较字符串,例如区分大小写等,并且反射器很快给了我们一个答案。知道这一点很有用。

如果您想对字符串进行区分大小写的比较,那么您可以使用 switch 语句,因为它比在 if/else 中执行 String.Compare 更快。 (编辑:阅读 What is quicker, switch on string or elseif on type? 以了解一些实际的性能测试)但是,如果您想要不区分大小写,那么最好使用 if/else,因为结果代码并不漂亮。

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

最好的经验法则是在有意义的情况下(认真地)使用 switch 语句,例如:

它提高了代码的可读性

您正在比较一系列值(浮点数、整数)或枚举

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

更新:

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

一点旁注:

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

把最有可能的分支放在第一位,即最常访问的分支

如果它们都可能发生,请按字母顺序列出它们,以便更容易找到它们。

永远不要对最后剩下的条件使用默认的包罗万象,这是懒惰的,并且会在代码的生命周期中导致问题。

使用默认的包罗万象来断言未知条件,即使它极不可能发生。这就是断言的好处。


在许多情况下,使用 ToLower() 是正确的解决方案,特别是如果有很多情况并且生成了哈希表。
“如果您需要操纵该值以输入 switch 语句(创建一个临时变量来切换),那么您可能应该使用 if/else 控制语句。” - 好建议,谢谢。
I
Iterator

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

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

- 一探究竟

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


D
Drew Noakes

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

我的 cs 教授建议你不要切换语句,因为人们经常忘记休息或错误地使用它。我不记得他到底说了什么,但在查看一些显示 switch 语句示例的开创性代码库(几年前)也有很多错误。


在 C# 中并不是真正的问题。请参阅:stackoverflow.com/questions/174155/… ... 并阅读 stackoverflow.com/questions/188461/… 以讨论为什么生活在恐惧中可能不是最好的政策...
E
Even Mien

我刚刚注意到的是,您可以结合 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”、“ else switch”、“else using”等。话虽如此,我确实喜欢它的外观,而且几乎看起来是有意的。 (免责声明:我没有尝试过所有这些,所以我可能错了!)
尼尔森,你是 100% 正确的。在发布此答案后,我想通了为什么会发生这种情况。
C
Community

我认为 Switch 比 If 条件更快,看看是否有类似的程序:

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

如果你必须做很多条件,但儿子 Switch Case 你必须输入

开关 ( no /10 ) and on case 0 = 1-9 , case 1 = 11-19 依此类推

会很容易

还有更多这样的例子!


G
Geen

switch 语句基本上是相等的比较。键盘事件比 switch 语句具有很大的优势,因为它易于编写和读取代码,然后 if elseif 语句会,缺少 {bracket} 也会带来麻烦。

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

如果(theAmountOfApples 大于 5 && theAmountOfApples 小于 10)如果(theAmountOfApples 大于 10 || theAmountOfApples == 100)出售您的苹果,则 if elseif 语句适用于多于一种解决方案。我不写 c# 或 c++,但我在学习 java 之前确实学习过它,它们是接近的语言。


N
Neil Meyer

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

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

您对 If 语句中可用的逻辑有更多的自由,但如果 If 语句变得笨拙或处理不当,可读性可能会受到影响。

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


s
sud

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