【问题标题】:C# Switch Statement: More efficient to not use default?C# Switch 语句:不使用默认值更高效?
【发布时间】:2012-03-17 10:04:52
【问题描述】:

我在 Visual Studio 中创建了一个 C# 方法,它只包含一个 switch 语句,每个 case 返回一个值。按照个人习惯,我放了类似下面的代码:

private string SwitchMethod(int num)
    {
        switch (num)
        {
            case 0:
                return "result 1";
            case 1:
                return "result 2";
            case 2:
                return "result 3";
        }
        return "no result";
    }

我的问题是:哪个代码会有更好的性能?上面或下面的代码,还是相同的?为什么?

我会假设由于编译器优化...它们可能只是相同...但我真的不知道。

private string SwitchMethod(int num)
    {
        switch (num)
        {
            case 0:
                return "result 1";
            case 1:
                return "result 2";
            case 2:
                return "result 3";
            default:
                return "no result";
        }
    }

修订: 看来我应该更具体一点:编译时......是否会生成效率较低的代码?

我意识到性能上的差异可能微不足道......我真的很好奇。

【问题讨论】:

  • 这是相当主观的。我避免从方法或函数中有多个退出点,但 YMMV。如果你真的很好奇,看看 IL。
  • 那编译?我不在我的编译器前面,但我几乎可以肯定需要一个默认值。
  • 如果您真的认为它不会发生,则默认抛出“不应该到达这里”的异常
  • 需要default 或switch() 之后的额外返回。
  • 好的,现在你已经修复了你的代码。而且您已经很好地编写了两种方式的代码。当你运行它们时,它们的表现如何?

标签: c# performance switch-statement


【解决方案1】:
public static string foo(int num)
        {
            switch (num)
            {
                case 0:
                    return "result 1";
                case 1:
                    return "result 2";
                case 2:
                    return "result 3";
            }
            return "no result";
        }

变成:

.method public hidebysig static string  foo(int32 num) cil managed
{
  // Code size       57 (0x39)
  .maxstack  1
  .locals init ([0] string CS$1$0000,
           [1] int32 CS$4$0001)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.1
  IL_0003:  ldloc.1
  IL_0004:  switch     ( 
                        IL_0017,
                        IL_001f,
                        IL_0027)
  IL_0015:  br.s       IL_002f
  IL_0017:  ldstr      "result 1"
  IL_001c:  stloc.0
  IL_001d:  br.s       IL_0037
  IL_001f:  ldstr      "result 2"
  IL_0024:  stloc.0
  IL_0025:  br.s       IL_0037
  IL_0027:  ldstr      "result 3"
  IL_002c:  stloc.0
  IL_002d:  br.s       IL_0037
  IL_002f:  ldstr      "no result"
  IL_0034:  stloc.0
  IL_0035:  br.s       IL_0037
  IL_0037:  ldloc.0
  IL_0038:  ret
} // end of method Program::foo

将返回值变为默认情况:

.method public hidebysig static string  foo(int32 num) cil managed
{
  // Code size       57 (0x39)
  .maxstack  1
  .locals init ([0] string CS$1$0000,
           [1] int32 CS$4$0001)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.1
  IL_0003:  ldloc.1
  IL_0004:  switch     ( 
                        IL_0017,
                        IL_001f,
                        IL_0027)
  IL_0015:  br.s       IL_002f
  IL_0017:  ldstr      "result 1"
  IL_001c:  stloc.0
  IL_001d:  br.s       IL_0037
  IL_001f:  ldstr      "result 2"
  IL_0024:  stloc.0
  IL_0025:  br.s       IL_0037
  IL_0027:  ldstr      "result 3"
  IL_002c:  stloc.0
  IL_002d:  br.s       IL_0037
  IL_002f:  ldstr      "result 4"
  IL_0034:  stloc.0
  IL_0035:  br.s       IL_0037
  IL_0037:  ldloc.0
  IL_0038:  ret
} // end of method Program::foo

完全一样。没有性能差异。我将“无结果”更改为结果 4,只是为了确保重新生成代码。显然 C# 编译器对其进行了优化,或者它最终是等价的。

【讨论】:

  • 非常感谢!原谅我的无知......但是你用什么来生成程序集?
  • @Brandon 有很多。例如 ilspy
  • ILDasm 带有 .NET Framework SDK。请记住,这是 IL 汇编代码,它不是 CPU 看到的。您可以使用 SDK NGen.exe 附带的工具来生成该程序集,但可能会根据硬件对其进行不同的优化。
  • @OskarKjellin - ILSpy 很好的提及。现在 Reflector 是 $$$,看到免费工具真是太酷了。
【解决方案2】:

对于其他情况均无效的情况,最好始终在 switch 的基础上包含默认情况。

这不是效率问题,因为如果遇到前面的情况之一 - 您的程序将不会检查任何其他情况(这相当于使用 if/else if/else - 最后的 else 是默认值)。

希望这会有所帮助。

【讨论】:

  • 您的回答还可以,但在理解您的意思时应该小心:stackoverflow.com/questions/445067/if-vs-switch-speed
  • 但是当编译时……会产生更多的代码吗?从而导致在不满足任何预期条件时执行更多指令?
  • 对不起 - 当然 switch 语句在执行速度方面更快。我的意思是就它们的工作方式而言,它们是等价的。 Brandon - 我想作为此基础的汇编代码将非常相似,并且不会执行额外的指令。很可能编译器实际上可能使您的代码在编译时尽可能高效,从而生成完全相同的汇编代码。
【解决方案3】:

代替函数和 switch 语句,最好使用通用 Dictionary,其中键是 KofaxEnvironment,值与从 switch 返回的值相同。比如:

Dictionary<KofaxEnvironment, string>

或

Dictionary<int, string>

我也不担心性能。正确性应该是您的首要目标。

但是,如果您确实坚持使用开关,请使用引发异常的默认值:

default:
    throw new ArgumentException("Serious programmer error!");

至于性能,切换默认值和返回返回值之间的差异(如果有的话)可以忽略不计。

【讨论】:

  • 这并不是我想要的……但这是个好主意……我将来肯定会使用它。更简洁的代码!
猜你喜欢
  • 2015-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
相关资源
最近更新 更多