它需要满足生成的 IL 的可验证性约束。请注意,unverifiable不一定意味着不正确。box
只要其安全上下文允许运行无法验证的代码,它就可以在没有指令的情况下正常工作。验证是保守的,并且基于固定的规则集(如可达性)。为了简化事情,他们选择不关心验证算法中泛型类型约束的存在。
第 9.11 节:对泛型参数的约束
...对泛型参数的约束仅限制可以实例化泛型参数的类型。
验证(参见第三部分)要求已知泛型参数通过满足约束提供的字段、属性或方法不能通过泛型参数直接访问/调用,除非它首先被装箱
(参见第三部分)或callvirt
指令以constrained
前缀指令为前缀。...
删除box
指令将导致无法验证的代码:
.method public hidebysig instance bool
F(!T r1,
!T r2) cil managed
{
ldarg.1
ldarg.2
ceq
ret
}
c:\Users\Mehrdad\Scratch>peverify sc.dll
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.1
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [c:\Users\Mehrdad\Scratch\sc.dll : A`1[T]::F][offset 0x00000002][fo
und (unboxed) 'T'] Non-compatible types on the stack.
1 Error(s) Verifying sc.dll
更新(对评论的回答):正如我上面提到的,可验证性不等于正确性(这里我从类型安全的角度谈论“正确性”)。可验证的程序是正确程序的严格子集(即所有可验证的程序都被证明是正确的,但有些正确的程序是不可验证的)。因此,可验证性是比正确性更强的属性。由于 C# 是图灵完备的语言,赖斯定理指出,证明程序是正确的在一般情况下是不可判定的。
让我们回到我的可达性类比,因为它更容易解释。假设您正在设计 C#。已经考虑过的一件事是何时发出有关无法访问代码的警告,并在优化器中完全删除该段代码,但是您将如何检测所有无法访问的代码?同样,赖斯定理说你不能对所有程序都这样做。例如:
void Method() {
while (true) {
}
DoSomething(); // unreachable code
}
这是 C# 编译器实际上警告的事情。但它没有警告:
bool Condition() {
return true;
}
void Method() {
while (Condition()) {
}
DoSomething(); // no longer considered unreachable by the C# compiler
}
在后一种情况下,人类可以证明控制流永远不会到达那条线。有人可能会争辩说,在这种情况下,编译器也可以静态证明DoSomething
是不可达的,但事实并非如此。为什么?关键是你不能对所有程序都这样做,所以你应该在某个点画线。在这个阶段,您必须定义一个可判定的属性并将其称为“可达性”。例如,为了可访问性,C# 坚持使用常量表达式,根本不会查看函数的内容。分析的简单性和设计的一致性是决定在哪里划线的重要目标。
回到我们的可验证性概念,这是一个类似的问题。与正确性不同,可验证性是可判定的属性。作为运行时设计者,您必须决定如何定义可验证性,基于性能考虑、易于实现、易于规范、一致性,使编译器可以轻松自信地生成可验证代码。像大多数设计决策一样,它涉及很多权衡。最终,CLI 设计人员决定,在检查可验证性时,他们根本不想过多关注通用约束。