提问人:Bogey 提问时间:3/29/2022 更新时间:3/29/2022 访问量:238
为什么 callvirt 用于在泛型类型的只读字段上调用方法
Why is callvirt used to call a method on a readonly field of generic type
问:
请考虑以下几点:
interface ISomething
{
void Call(string arg);
}
sealed class A : ISomething
{
public void Call(string arg) => Console.WriteLine($"A, {arg}");
}
sealed class Caller<T> where T : ISomething
{
private readonly T _something;
public Caller(T something) => _something = something;
public void Call() => _something.Call("test");
}
new Caller<A>(new A()).Call();
对 Caller<A> 的调用。Call,以及它对 A.Call 的嵌套 tcall 是通过 callvirt 指令提交的。
但是为什么?这两种类型都是完全已知的。除非我误解了什么,否则这里不应该使用 call 而不是 callvirt 吗?
如果是这样 - 为什么不这样做?这仅仅是编译器没有完成的优化,还是背后有什么具体原因?
答:
你错过了两件事。
第一种是对接收器进行空检查,而没有。这意味着在接收器上使用将引发一个 ,而会愉快地调用该方法并作为第一个参数传递,这意味着该方法将获得一个参数,即 。callvirt
call
callvirt
null
NullReferenceException
call
null
this
null
听起来令人惊讶?是的。在非常早期的 .NET 版本中,IIRC 是按照您建议的方式使用的,人们对如何在方法内部感到非常困惑。编译器切换到强制运行时预先执行 null 检查。call
this
null
callvirt
只有少数几个地方编译器会发出:call
- 静态方法。
- 非虚拟结构方法。
- 调用基方法或基构造函数(我们知道接收方不是,并且我们也明确不想进行虚拟调用)。
null
- 当编译器确定接收器不为空时,例如 其中是非虚拟的。
foo?.Method()
Method
最后一点特别意味着制定方法是一个二进制破坏性的变化。virtual
只是为了好玩,请参阅此检查中此 == null
。String.Equals
第二件事是,这不是一个虚拟呼叫,而是一个受约束的虚拟呼叫。在它之前会出现一个受约束
的操作码。_something.Call("test");
受约束的虚拟调用是用泛型引入的。问题是对类和结构的方法调用有点不同:
- 对于类,您可以加载类引用(例如,使用 ),然后使用 / 。
ldloc
call
callvirt
- 对于结构,您可以加载结构的地址(例如,使用 ),然后使用 .
ldloc.a
call
- 要调用结构上的接口方法,或在 上定义的方法,您需要加载结构值(例如,用 ),装箱,然后使用 / 。
object
ldloc
call
callvirt
如果泛型类型不受约束(即它可以是类或结构),编译器不知道该怎么做:它应该使用 or ?它应该装箱还是不装箱? 或?ldloc
ldloc.a
call
callvirt
受约束的虚拟调用将此责任转移到运行时。引用上面的文档:
当一条指令以 为前缀时,该指令将按如下方式执行:
callvirt
method
constrained
thisType
- If 是引用类型(而不是值类型),则取消引用并作为指向 的 'this' 指针传递。
thisType
ptr
callvirt
method
- If 是值类型和 implements 则作为指向指令的 'this' 指针未经修改地传递,用于实现 by 。
thisType
thisType
method
ptr
call
method
method
thisType
- If 是值类型且未实现,则被取消引用、装箱并作为指向指令的“this”指针传递。
thisType
thisType
method
ptr
callvirt
method
最后一种情况只有在 上定义 时才会发生,或者 上定义,并且未被 覆盖。在这种情况下,装箱会导致制作原始对象的副本。但是,由于 、 和 的方法都不能修改对象的状态,因此无法检测到这一事实。
method
System.Object
System.ValueType
System.Enum
thisType
System.Object
System.ValueType
System.Enum
评论
.Call()
null
评论
Caller
T
T
Caller<A>
Caller<T>