C#开关语句限制-为什么?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (17)

在编写开关语句时,对于在Case语句中可以打开的内容似乎有两个限制。

  Type t = typeof(int);

  switch (t) {

    case typeof(int):
      Console.WriteLine("int!");
      break;

    case typeof(string):
      Console.WriteLine("string!");
      break;

    default:
      Console.WriteLine("unknown!");
      break;
  }

在这里,Switch()语句失败时使用了‘a整型预期值’,case语句失败时使用了‘A常量值’。

为何会有这些限制,其背后的理据为何?我看不出有什么理由只接受静态分析,以及为什么打开的值必须是整数(即原语)。

提问于
用户回答回答于

实际上,C#Switch语句是总是一个固定的时间分支。

在某些情况下,编译器将使用CILSwitch语句,它实际上是一个使用跳转表的恒定时间分支。

这实际上很容易通过编写各种C#Switch语句来验证,有些语句稀疏,有些很密集,并使用ildasm.exe工具查看结果CIL。

用户回答回答于

重要的是不要混淆C#Switch语句和CIL开关指令。

CIL开关是一个跳转表,它需要索引到一组跳转地址。

只有当C#开关的情况相邻时,这才有用:

case 3: blah; break;
case 4: blah; break;
case 5: blah; break;
case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;

(需要一个大小为3000个条目的表,只使用3个插槽)

对于不相邻的表达式,编译器可能会开始执行线性if-ell-if-etc检查.

对于较大的不相邻表达式集,编译器可能以二叉树搜索开始,最后是if-否则-if-否则最后几个项。

有了包含相邻项块的表达式集,编译器可以进行二叉树搜索,最后是一个CIL开关。

它充满了“Mays”和“mights”,它依赖于编译器(可能与Mono或旋翼不同)。

我使用相邻的情况在我的机器上复制了结果:

执行10路开关的总时间,10000次迭代(Ms):25.1383 每10路开关(Ms)的大约时间:0.00251383 执行50路开关的总时间,10000次迭代(Ms):26.593 每50路开关(Ms)的大约时间:0.0026593 执行5000路开关的总时间,10000次迭代(Ms):23.7094 每5000路开关(Ms)的大约时间:0.00237094 执行50000路开关的总时间,10000次迭代(Ms):20.0933 每50000路开关(Ms)的大约时间:0.00200933

然后我还使用了不相邻的案例表达式:

执行10路开关的总时间,10000次迭代(Ms):19.6189 每10路开关(Ms)的大约时间:0.00196189 执行500路开关的总时间,10000次迭代(Ms):19.1664 每500路开关(Ms)的大约时间:0.00191664 执行5000路开关的总时间,10000次迭代(Ms):19.5871 每5000路开关(Ms)的大约时间:0.00195871 不相邻的5万CASE开关语句不会编译。 “表达式太长或太复杂,无法在‘ConsoleApplication1.Program.Main(String[])’附近编译undefined

有趣的是,二叉树搜索看起来比CIL开关指令稍微快一些。

布莱恩,你用了“常量“,从计算复杂性理论的角度看,它具有非常明确的意义。当简单的相邻整数示例可能产生被认为是O(1)(常数)的CIL时,稀疏的例子是O(Logn)(对数),聚集的例子介于两者之间,小的例子是O(N)(线性)。

这甚至不能解决字符串的情况,在这种情况下,静态Generic.Dictionary<string,int32>可能会被创建,并且在第一次使用时会承受一定的开销。这里的性能将取决于Generic.Dictionary

最后,在现代系统中,C#与整数表达式的切换是一种亚微秒操作,通常不值得担心。

当然,这些时间将取决于机器和条件。我不会注意这些计时测试,我们所讨论的微秒时间与任何正在运行的“真实”代码相比都相形见绌(您必须包括一些“真实代码”,否则编译器将优化分支),或者系统中的抖动。

我已经检查了在我的x86机器上实际执行的最后CPU指令,并且可以确认一个简单的相邻的SET开关,这样做如下:

  jmp     ds:300025F0[eax*4]

其中,二叉树搜索包含:

  cmp     ebx, 79Eh
  jg      3000352B
  cmp     ebx, 654h
  jg      300032BB
  …
  cmp     ebx, 0F82h
  jz      30005EEE

扫码关注云+社区