C#编译器+带有装箱+约束的通用代码

时间:2009-11-02 12:54:53

标签: c# generics cil boxing unboxing

让我们检查为以下通用方法生成的MSIL代码:

public static U BoxValue<T, U>(T value)
  where T : struct, U
  where U : class
{
  return value;
}

查找

.method public hidebysig static !!U  BoxValue<valuetype .ctor
 ([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed
{
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  unbox.any  !!U
  IL_000b:  ret
}

但是对于上面的通用代码,更有效的IL表示应该是:

  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  ret

从约束条件可知,该值被装入引用类型Unbox.any操作码完全是冗余的,因为在box操作码后,IL堆栈的值已经是!!U的有效引用,可以在没有任何拆箱的情况下使用。

为什么C#3.0编译器不使用约束元数据来发出更高效的通用代码? Unbox.any提供了一个小的开销(只比4x-5x慢),但为什么不在这种情况下发出更好的代码呢?

2 个答案:

答案 0 :(得分:6)

看起来编译器会这样做,因为验证程序存在一些问题。

您希望编译器生成的IL不可验证,因此C#编译器无法生成它(“不安全”上下文之外的所有C#代码都应该是可验证的。)

“验证类型兼容性”的规则在Ecma规范的第III部分第1.8.1.2.3节中给出。

他们说使用以下规则,类型'S'与类型'T'或(S:= T)验证兼容:

  1. [:= is reflexive]对于所有验证类型S,S:= S
  2. [:= is transitive]对于所有验证类型S,T和U,如果S:= T且T:= U,则S:= U。
  3. S:= T如果S是T的基类,或者T实现的接口不是值类型。
  4. object:= T如果T是接口类型。
  5. S:= T如果S和T都是接口,则T的实现需要实现 S
  6. S:=如果S是对象类型或接口
  7. ,则为null
  8. S []:= T []如果S:= T且数组是两个向量(从零开始,排名第一)或两者都不 是一个向量,两者都有相同的排名。 (此规则处理数组协方差。)
  9. 如果S和T是方法指针,那么S:= T如果是签名(返回类型,参数类型和 调用约定)是相同的。
  10. 在这些规则中,唯一可能适用于此情况的规则是#3。

    但是,#3不适用于您的代码,因为'U'不是'T'的基类,并且它不是'T'的基接口,因此'或'检查返回false。 / p>

    这意味着需要执行SOME指令,以便以通过验证程序的方式将盒装T转换为U.

    我同意您的观点,即应更改验证规则,以便生成您想要的代码实际上是可验证的。

    但从技术上讲,编译器根据ECMA规范做了“正确”的事情。

    你应该向微软的某人提出错误。

答案 1 :(得分:3)

这些限制看起来很奇怪:

where T : struct, U
where U : class

T是值类型,但同时必须从作为引用类型的U继承。我想知道什么类型可以满足上述约束,并允许我们称这种方法。