几天前,当我在溢出时为this question写一个答案时,我对C#编译器感到有些惊讶,他没有按照我的预期去做。请查看以下内容以编写代码段:
首先:
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = (ICollection<object>)array;
col.Contains(null);
}
第二
object[] array = new object[1];
for (int i = 0; i < 100000; i++)
{
ICollection<object> col = array;
col.Contains(null);
}
两个片段之间代码的唯一区别是对ICollection&lt; object&gt;的强制转换。因为object []实现了ICollection&lt; object&gt;接口显式,我期望两个片段编译成相同的IL,因此是相同的。但是,当对它们进行性能测试时,我注意到后者的速度是前者的6倍。
在比较两个片段中的IL之后,我注意到两种方法都是相同的,除了第一个片段中的 castclass IL指令。
对此感到惊讶我现在想知道为什么C#编译器在这里不“聪明”。事情从来没有像看起来那么简单,那么为什么C#编译器在这里有点天真?
答案 0 :(得分:32)
我的猜测是你在优化器中发现了一个小错误。数组中有各种特殊情况代码。谢谢你引起我的注意。
答案 1 :(得分:4)
这是一个粗略的猜测,但我认为这是关于Array与其通用IEnumerable的关系。
在.NET Framework 2.0版中, Array类实现了 System.Collections.Generic.IList, 了System.Collections.Generic.ICollection, 和 System.Collections.Generic.IEnumerable 通用接口。该 实现被提供给数组 在运行时,因此不是 对文档构建可见 工具。结果,通用 接口不会出现在 Array的声明语法 上课,并没有参考 接口成员的主题 只能通过将数组转换为 通用接口类型(显式 接口实现)。钥匙 当你施展时要注意的事情 数组到这些接口之一是 添加,插入或的成员 删除元素抛出 NotSupportedException异常。
请参阅MSDN Article。
目前尚不清楚这是否与.NET 2.0+相关,但在这种特殊情况下,如果编译器仅在运行时变为有效,编译器无法优化您的表达式将是完全合理的。
答案 2 :(得分:2)
这看起来不仅仅是编译器中错过了强制转换的机会。如果你这样写它会起作用:
ICollection<object> col = array as ICollection<object>;
这表明它过于保守,因为强制转换会抛出异常。但是,当您转换为非泛型ICollection时,它确实有效。我得出结论,他们只是忽略了它。
这里有一个更大的优化问题,JIT编译器不应用循环不变的提升优化。它应该重写这样的代码:
object[] array = new object[1];
ICollection<object> col = (ICollection<object>)array;
for (int i = 0; i < 100000; i++)
{
col.Contains(null);
}
例如,这是C / C ++代码生成器中的标准优化。尽管如此,JIT优化器无法在发现这种可能的优化所需的分析上燃烧很多周期。对此的满意是优化的托管代码仍然可以调试。并且C#程序员仍然可以编写高性能代码。