为了账号安全,请及时绑定邮箱和手机立即绑定

C#switch语句限制 - 为什么?

C#switch语句限制 - 为什么?

C#
富国沪深 2019-07-26 16:21:20
C#switch语句限制 - 为什么?在编写switch语句时,在case语句中可以打开的内容似乎存在两个限制。例如(是的,我知道,如果你正在做这种事情,这可能意味着你的面向对象(OO)架构是不确定的 - 这只是一个人为的例子!),  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()语句失败,带有'一个预期的整数类型的值',case语句失败并带有'a expected value is expected'。为什么会有这些限制,以及基本理由是什么?我看不出有任何理由switch语句具有只能屈从于静态分析,为什么在接通的值必须是完整的(即原语)。理由是什么?
查看完整描述

3 回答

?
冉冉说

TA贡献1877条经验 获得超1个赞

这是我的原帖,引发了一些争论...... 因为它错了

switch语句与if-else语句不同。每个案例必须是唯一的并且静态评估。无论您有多少个案例,switch语句都会执行一个恒定时间分支。if-else语句计算每个条件,直到找到一个为真。


实际上,C#switch语句并不总是一个恒定时间分支。

在某些情况下,编译器将使用CIL开关语句,该语句确实是使用跳转表的恒定时间分支。然而,在Ivan Hamilton指出的稀疏情况下,编译器可能完全生成其他东西。

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


查看完整回答
反对 回复 2019-07-26
?
慕桂英4014372

TA贡献1871条经验 获得超13个赞

重要的是不要将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-else-if-else检查。

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

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

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

我使用相邻的案例在我的机器上复制了你的结果:

执行10路开关的总时间,10000次迭代(ms):
每10路开关25.1383 近似时间(ms):0.00251383

执行50路开关的总时间,10000次迭代(ms):
每50路开关的大约时间为26.593 (ms):0.0026593

执行5000路开关的总时间,10000次迭代(ms):23.7094 
每5000路开关的近似时间(ms):0.00237094

执行50000路开关的总时间,10000次迭代(ms):20.0933 
每50000路开关的近似时间(ms):0.00200933

然后我也使用了非相邻的case表达式:

执行10路开关的总时间,10000次迭代(ms):19.6189 
每10路开关的近似时间(ms):0.00196189

执行500路开关的总时间,10000次迭代(ms):
每个500路开关的近似时间为19.1664 (ms):0.00191664

执行5000路开关的总时间,10000次迭代(ms):
每5000路开关的大约时间为19.5871 (ms):0.00195871

不相邻的50,000个案例切换语句将无法编译。
“在'ConsoleApplication1.Program.Main(string [])'附近编译表达式太长或太复杂

这里有趣的是,二叉树搜索比CIL切换指令更快(可能不是统计上)。

Brian,你使用了“ 常量 ” 这个词,从计算复杂性理论的角度来看,它具有非常明确的含义。虽然简化的相邻整数示例可以产生被认为是O(1)(常数)的CIL,但稀疏示例是O(log n)(对数),聚类示例介于两者之间,小例子是O(n)(线性) )。

这甚至不能解决Generic.Dictionary<string,int32>可能会创建静态的String情况,并且在首次使用时会遇到明确的开销。这里的表现将取决于表现Generic.Dictionary

如果检查C#语言规范(不是CIL规范),你会发现“15.7.2 switch语句”没有提到“常量时间”或底层实现甚至使用CIL开关指令(要非常小心假设这样的事情)。

在一天结束时,针对现代系统上的整数表达式的C#切换是亚微秒操作,通常不值得担心。


当然,这些时间将取决于机器和条件。我不会注意这些时序测试,我们所讨论的微秒持续时间与正在运行的任何“真实”代码相比相形见绌(并且您必须包含一些“真实代码”,否则编译器将优化分支),或者系统中的抖动。我的答案基于使用IL DASM来检查C#编译器创建的CIL。当然,这不是最终的,因为CPU运行的实际指令随后由JIT创建。

我检查了在我的x86机器上实际执行的最终CPU指令,并且可以确认一个简单的相邻设置开关,例如:

  jmp     ds:300025F0[eax*4]

二叉树搜索满满的地方:

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


查看完整回答
反对 回复 2019-07-26
  • 3 回答
  • 0 关注
  • 733 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信