在C#中使用if / else和switch-case之间有什么显着区别吗?
在C#中使用switch
语句与if/else
什么好处/不利之处。 我无法想象有什么大的差别,除了你的代码的外观。
是否有任何理由为什么生成的IL或相关的运行时性能会有根本的不同?
相关: 什么是更快,打开字符串或elseif的类型?
在调试或兼容模式下,SWITCH语句只产生与IF相同的程序集。 在发行版中,它将被编译到跳转表(通过MSIL'switch'语句) – 这是O(1)。
C#(与许多其他语言不同)也允许打开字符串常量 – 这有点不同。 为任意长度的字符串建立跳转表显然是不实际的,因此大多数情况下这样的开关将被编译成IF堆栈。
但是,如果条件数量足够大以涵盖开销,C#编译器将创建一个HashTable对象,用字符串常量填充它,然后在该表上进行查找,然后跳转。 散列表查找不是严格的O(1),并且具有明显的恒定成本,但是如果个案标签的数目很大,那么它将比IF中的每个字符串常量的速度快得多。
总结一下,如果条件数量大于5,那么更喜欢用IF来切换,否则用更好的方法。
一般情况下(考虑到所有的语言和所有的编译器),一个switch语句CAN SOMETIMES比if / else语句更有效率,因为编译器很容易从switch语句中生成跳转表。 给定适当的约束条件,if / else语句也可以做同样的事情,但是这样做要困难得多。
在C#的情况下,这也是如此,但其他原因。
对于大量的字符串,使用switch语句具有显着的性能优势,因为编译器将使用散列表来实现跳转。
用少量的字符串,两者之间的表现是相同的。
这是因为在这种情况下,C#编译器不会生成跳转表。 相反,它会生成相当于IF / ELSE块的MSIL。
有一个“开关语句”MSIL指令,当jitted将使用一个跳转表来实现一个switch语句。 它只适用于整数类型,但是(这个问题询问关于字符串)。
对于少量的字符串,编译器生成IF / ELSE块的效率更高,然后使用散列表。
当我最初注意到这一点的时候,我做了这样的假设,因为IF / ELSE块被使用了少量的字符串,所以编译器对大量的字符串进行了相同的转换。
这是错误的。 “IMA很友好地向我指出这一点(呃,他对此不是很好,但他是对的,我错了,这是重要的部分)
我还做了一个关于在MSIL中缺少“switch”指令的假设(我想,如果有一个switch原语,为什么不把它用在一个哈希表中,所以一定不能有switch原语。 …)。 这是错误的,而我也是非常愚蠢的。 “IMA”再次向我指出这一点。
我在这里做了更新,因为它是评分最高的帖子,也是被接受的答案。
但是,我已经把它做成了社区维基,因为我认为我不值得被错误的REP。 如果你有机会,请投票“ima”的职位。
选择switch
三个理由:
-
一个以本机代码为目标的编译器通常可以将一个switch语句编译成一个条件分支加上一个间接跳转,而一个
if
序列if
需要一系列条件分支 。 根据病例的密度,已经写了很多关于如何有效编译病例报告的学术论文。 有些是从lcc编译器页面链接的。 (Lcc拥有更具创新性的开关编译器之一。) -
switch语句是相互排斥的选项之间的一种选择, switch语法使得这个控制流对程序员来说更加透明,然后是if-then-else语句的嵌套。
-
在某些语言中,绝对包括ML和Haskell, 编译器会检查是否遗漏了任何情况 。 我认为这个功能是ML和Haskell的主要优点之一。 我不知道如果C#可以做到这一点。
一个轶事:我听到托尼·霍尔(Tony Hoare)在一次演讲中所获得的终身成就奖,他说在他的职业生涯中所做的所有事情中,有三个是他最为自豪的:
- 发明Quicksort
- 发明switch语句(Tony称之为
case
语句) - 开始和结束他的行业生涯
我无法想象没有switch
生活 。
实际上,switch语句更有效率。 编译器会将其优化到查找表,其中if / else语句不能。 反过来说,switch语句不能用于变量值。
你不能这样做:
switch(variable) { case someVariable break; default: break; }
它一定要是
switch(variable) { case CONSTANT_VALUE; break; default: break; }
编译器会将几乎所有的东西都优化成相同的代码,只是细微的差别(Knuth,任何人?)。
不同之处在于,如果else语句串联在一起,switch语句比十五个语句更干净。
朋友不要让朋友堆放if-else语句。
我没有看到其他人提出(显而易见的)的观点,即转换声明所假设的效率优势取决于各种情况几乎相同的可能性。 在一个(或几个)值更有可能的情况下,通过确保最常见的情况被首先检查,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%的时间内为零,则“if-else”代码可以是基于开关的代码的两倍。 即使编译器将“开关”变成了某种巧妙的表驱动转换,它仍然不会像检查零一样快。
通常它会看起来更好 – 即将更容易理解正在发生的事情。 考虑到性能方面的好处将是极其微小的,代码的观点是最重要的区别。
所以,如果if / else看起来更好,那么使用它,否则使用switch语句。
小题目,但我经常担心(更经常看到), if
/ else
和switch
语句太多,太多的情况下。 这些经常伤害可维护性。
常见的罪魁祸首包括:
- 在多个if语句中做太多的事情
- 更多的案例陈述比人力可能分析
- if评估中的条件太多,以知道正在查找什么
修理:
- 提取到方法重构。
- 使用带有方法指针的字典而不是案例,或者使用IoC来增加可配置性。 方法工厂也是有帮助的。
- 将条件提取到自己的方法
这实际上并没有回答你的问题,但是考虑到编译版本之间几乎没有什么区别,我会敦促你用最能描述你的意图的方式编写你的代码。 编译器不仅有更好的机会做你期望的事情,而且会让别人更容易维护你的代码。
如果你的意图是根据一个变量/属性的值来分支你的程序,那么一个switch语句最能代表这个意图。
如果你的意图是根据不同的变量/属性/条件来分支你的程序,那么if / else if链最能代表这个意图。
我会承认cody对于忘记break命令是正确的,但是我几乎同样经常看到人们在错误的地方做了复杂的事情,所以应该在条件语句中的行不是。 这是我总是在我的if语句中包含{}的原因之一,即使其中有一行。 不仅更容易阅读,但如果我需要在条件中添加另一行,我不能忘记添加它。
如果您只是使用if或else语句,则基本解决方案使用的是比较? 操作者
(value == value1) ? (type1)do this : (type1)or do this;
您可以在交换机上执行或例行操作
switch(typeCode) { case TypeCode:Int32: case TypeCode.Int64: //dosomething here break; default: return; }
不仅仅是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和相等性测试。
兴趣问题。 这几个星期前在工作,我们找到了一个答案写了一个例子片段,并在.NET反射器(反射器真棒!!我喜欢它)查看它。
这是我们发现的:除了字符串以外的其他任何有效的switch语句都会被编译成IL语句作为switch语句。 但是,如果它是一个字符串,它会被重写为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()
相比,即时编译器可以进行进一步的优化。 这是一个微观优化,然而在一个紧密的循环中,它可能是有用的。
有一点注意:
为了提高开关语句的可读性,请尝试以下操作:
- 把最可能的分支,即最访问
- 如果它们都可能发生,按字母顺序列出,以便找到它们更容易。
- 从来没有使用默认的捕获所有的最后剩下的条件,这是懒惰的,并会在代码的生活中稍后的问题。
- 使用默认的catch-all来声明一个未知的条件,即使它不太可能发生。 这是主张是有利的。
switch语句肯定比if更快。 BlackWasp已经提供了speedtest
http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
– 一探究竟
但是很大程度上取决于你想要解释的可能性,但是我尽可能地使用switch语句。
我的cs教授建议你不要改变陈述,因为人们经常忘记这个中断或者错误地使用它。 我不能确切地记得他所说的话,但是看到一些开创性的代码基础(几年前)的转换语句的例子也有很多错误。
我刚才注意到的是,你可以结合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; }
按照这个链接, IF vs Switch比较使用switch和if语句的迭代测试,就像是进行了1,000,000,000次迭代一样, Switch Statement = 43.0s和If Statement = 48.0s
这实际上是每秒20833333个迭代,所以,我们是否真的需要更多的关注,
PS:只是要知道小条件列表的性能差异。
我认为开关比如果条件喜欢看更快如果有一个程序如:
写一个程序输入任何数字(1 – 99之间),并检查它是在哪个槽a)1 – 9然后插槽一b)11 – 19然后插槽二c)21 – 29然后插槽三,等到89 – 99
然后如果你必须做很多条件,但儿子切换的情况下,你只需要输入
开关(no / 10)
案例0 = 1-9,案例1 = 11-19等等
这将是如此简单
还有更多这样的例子!
一个转换语句基本上是一个平等的比较。 键盘事件比开关语句有很大的优势,当易于编写和读取代码,然后一个if if语句会,缺少一个{括号}也可以得到麻烦。
char abc; switch(abc) { case a: break; case b: break; case c: break; case d: break; }
if(else)if语句对于多于一个的解决方案是有用的(如果AmountOfApples大于5 && theAmountOfApples小于10)保存你的苹果否则如果(theAmountOfApples大于10 || theAmountOfApples == 100)卖你的苹果。 我不写c#或c + +,但我学习之前,我学习了Java,他们是密切的语言。
我知道这不完全是一个问题,但我真的需要指出的是,当您考虑到该级别的效率时,可能需要在代码中进行更多的抽象。 您将不再需要切换的情况下,特别是如果它包含逻辑。 (我的例子在PHP中)。
$feeMapping = [ 1000 => 1, 2000 => 2, 3000 => 3, 4000 => 4, 5000 => 5, 6000 => 6, 7000 => 7 ]; function findFee($feeMapping, $amount) { foreach ($feeMapping as $fee => $value) { if ($value >= $amount) { return $fee; } } return 7; } $feeValue = findFee($feeMapping, 200);
现在看看类似代码的冗余!
if ($amount >= 1000) { return 1; } elseif ($amount >= 2000) { return 2; } elseif ($amount >= 3000) { return 3; } elseif ($amount >= 4000) { return 4; } elseif ($amount >= 5000) { return 5; } elseif ($amount >= 6000) { return 6; } else { return 7; }
开关语句的一个可能的缺点是缺少多个条件。 在交换机中,if(else)可以有多个条件,但不能有多个case条件。
开关语句不适用于超出简单布尔方程/表达式范围的逻辑操作。 对于那些布尔方程/表达式来说,这是非常合适的,但不适用于其他的逻辑运算。
If语句中可用的逻辑具有更多的自由度,但是如果If语句变得笨拙或处理得不好,则可读性会受到影响。
这两个地方都取决于你所面临的情况。