Bog*_*gey 1 c# cil intermediate-language
考虑以下:
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();
Run Code Online (Sandbox Code Playgroud)
对 Caller<A>.Call 的调用以及对 A.Call 的嵌套 tcall 都是通过 callvirt 指令进行的。
但为什么?这两种类型都是众所周知的。除非我误解了什么,否则这里不应该使用 call 而不是 callvirt 吗?
如果是这样 - 为什么不这样做?这仅仅是编译器未完成的优化,还是背后有任何特定原因?
你错过了两件事。
第一个是callvirt对接收器进行空检查,而call不会。这意味着在接收器callvirt上使用null将引发NullReferenceException,而call将愉快地调用该方法并null作为第一个参数传递,这意味着该方法将获得一个this参数null。
听起来很令人惊讶吗?这是。IIRC 在非常早期的 .NET 版本中call 是按照您建议的方式使用的,人们对如何this在null方法内部感到非常困惑。编译器切换为callvirt强制运行时预先执行空检查。
编译器只有少数几个地方会发出call:
null,并且我们也明确不想进行虚拟调用)。foo?.Method()其中Method是非虚拟的。最后一点特别意味着创建一个方法virtual是一个二进制破坏性的改变。
只是为了好玩,请参阅in的此检查this == nullString.Equals。
第二件事是,这_something.Call("test");不是虚拟调用,而是受约束的虚拟调用。在它之前出现一个constrained操作码。
受约束的虚拟调用是通过泛型引入的。问题在于类和结构上的方法调用有点不同:
ldloc),然后使用call/ callvirt。ldloc.a(例如使用),然后使用call。object,您需要加载结构体值(例如使用ldloc),将其装箱,然后使用call/ callvirt。如果泛型类型不受约束(即它可以是类或结构),编译器不知道该怎么做:应该使用ldlocorldloc.a吗?应该装箱还是不装箱?call或者callvirt?
受约束的虚拟调用将此责任转移到运行时。引用上面的文档:
当一条
callvirtmethod指令带有 前缀时constrainedthisType,该指令执行如下:
- 如果
thisType是引用类型(而不是值类型),则ptr取消引用并作为 'this' 指针传递到callvirtofmethod。- 如果
thisType是值类型并thisType实现methodthen 则ptr作为指向指令的“this”指针未经修改地传递,用于bycallmethod的实现。methodthisType- 如果
thisType是值类型并且thisType未实现methodthenptr则被取消引用、装箱并作为“this”指针传递给指令callvirtmethod。仅当在、或
method上定义并且未被 覆盖时,最后一种情况才会发生。在这种情况下,装箱会导致生成原始对象的副本。但是,由于、、 和的方法都没有修改对象的状态,因此无法检测到这一事实。System.ObjectSystem.ValueTypeSystem.EnumthisTypeSystem.ObjectSystem.ValueTypeSystem.Enum
| 归档时间: |
|
| 查看次数: |
269 次 |
| 最近记录: |