4

让我们检查为以下通用方法生成的 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.anyopcode 是完全多余的,因为在boxopcode 之后,IL 堆栈中的值已经是对 的有效引用!!U,无需任何拆箱即可使用。

为什么 C# 3.0 编译器不使用约束元数据来生成更高效的通用代码?Unbox.any 的开销很小(只是慢了 4 到 5 倍),但为什么不在这种情况下发出更好的代码呢?

4

2 回答 2

7

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

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

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

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

  1. [:= 是自反的] 对于所有验证类型 S, S := S
  2. [:= 是可传递的] 对于所有验证类型 S、T 和 U,如果 S := T 和 T := U,则 S := U。
  3. S := T 如果 S 是 T 的基类或由 T 实现的接口且 T 不是值类型。
  4. object := T 如果 T 是接口类型。
  5. S := T 如果 S 和 T 都是接口并且 T 的实现需要 S 的实现
  6. S := null 如果 S 是对象类型或接口
  7. S[] := T[] 如果 S := T 并且数组要么都是向量(从零开始,排名一),要么都不是向量并且都具有相同的排名。(此规则处理数组协方差。)
  8. 如果 S 和 T 是方法指针,则 S := T 如果签名(返回类型、参数类型和调用约定)相同。

在这些规则中,唯一可能适用于这种情况的是#3。

但是,#3 不适用于您的代码,因为“U”不是“T”的基类,也不是“T”的基接口,因此“或”检查返回 false。

这意味着需要执行 SOME 指令才能将装箱的 T 转换为 U 以通过验证程序。

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

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

您应该向 Microsoft 的某个人提交错误。

于 2009-11-14T19:20:41.410 回答
3

这些约束看起来很奇怪:

where T : struct, U
where U : class

T 是值类型,但同时必须从 U 继承,U 是引用类型。我想知道哪些类型可以满足上述约束并允许我们调用此方法。

于 2009-11-02T13:12:32.803 回答