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语句失败'期望值为常数'。
为什么这些限制已经到位,以及根本的理由是什么? 我没有看到为什么switch语句只能屈服于静态分析的任何理由,以及为什么要打开的值必须是整数(即原始的)。 什么是理由?
这是我原来的帖子,引发了一些争论...... 因为它是错误的 :
switch语句与if-else语句不同。 每个案例必须是唯一的,并进行静态评估。 无论您拥有多少个案例,switch语句都会执行一个常量时间分支。 if-else语句评估每个条件,直到找到一个为真的条件。
事实上,C#switch语句并不总是一个常量时间分支。
在某些情况下,编译器会使用CIL switch语句,这个语句实际上是一个使用跳转表的常量时间分支。 然而,在伊万汉密尔顿所指出的稀少情况下,编译器可能会完全生成其他内容。
这实际上很容易通过编写各种C#开关语句进行验证,一些稀疏,一些密集,并使用ildasm.exe工具查看生成的CIL。
不要将C#switch语句与CIL switch指令混淆。
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):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
然后我也使用了非相邻的case表达式:
执行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
一个不相邻的50,000个case switch语句不会被编译。
“表达式太长或者很复杂,无法在'ConsoleApplication1.Program.Main(string [])'附近编译”
这里有趣的是,二叉树搜索似乎有点(可能不是统计上)比CIL开关指令更快。
布赖恩,你已经使用了“ 常量 ”这个词,从计算复杂性理论的角度来看,它具有非常明确的含义。 虽然简单的相邻整数示例可能会产生被认为是O(1)(常数)的CIL,但稀疏示例是O(log n)(对数),集群示例介于两者之间,小示例是O(n)(线性)。
这甚至没有解决String情况,在这种情况下可能会创建一个静态的Generic.Dictionary<string,int32>
,并且在第一次使用时将承受一定的开销。 这里的性能将取决于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
想到的第一个原因是历史的 :
由于大多数C,C ++和Java程序员都不习惯拥有这样的自由,他们并不要求它们。
另一个更有效的原因是语言复杂性会增加 :
首先,是否应该将对象与.Equals()
或==
运算符进行比较? 两者在某些情况下都有效。 我们应该引入新的语法来做到这一点吗? 我们应该让程序员介绍他们自己的比较方法吗?
另外,允许开启对象会破坏关于switch语句的基本假设 。 控制switch语句有两条规则,即如果允许对象打开,编译器将无法执行(请参阅C#3.0版语言规范,第8.7.2节):
在假设的情况下考虑这个代码示例,允许非常量事件值:
void DoIt()
{
String foo = "bar";
Switch(foo, foo);
}
void Switch(String val1, String val2)
{
switch ("bar")
{
// The compiler will not know that val1 and val2 are not distinct
case val1:
// Is this case block selected?
break;
case val2:
// Or this one?
break;
case "bar":
// Or perhaps this one?
break;
}
}
代码将做什么? 如果案例陈述重新排序会怎样? 事实上,C#使交换机非法通过的原因之一是交换机语句可以任意重新排列。
这些规则是有原因的 - 所以程序员可以通过查看一个case block来确定block输入的确切条件。 当上述switch语句增长到100行或更多时(它会),这样的知识是无价的。
链接地址: http://www.djcxy.com/p/84405.html上一篇: C# switch statement limitations
下一篇: How can a variable be used when its definition is bypassed?