以下示例程序是我试图掌握ldvirtftn
操作码的用法。您会看到名称表明这是在将虚函数指针加载到堆栈时使用的操作码。在示例代码中,我创建了一个包含2个静态方法Ldftn
和Ldvirtftn
的类型,这两个方法都返回Base.Method()
的开放委托,第一个函数Ldftn
使用ldftn
操作码,并且意外工作,因为Base.Method
是虚拟的。第二种方法使用Ldvirtftn
,显然创建了一个无效的程序。我究竟做错了什么?除了困惑之外,这个操作码的目的是什么?
public class Base
{
public virtual void Method()
{
Console.WriteLine("Base");
}
}
public class Child : Base
{
public override void Method()
{
Console.WriteLine("Child");
}
}
class Program
{
static void Main(string[] args)
{
AssemblyBuilder ab =AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"),AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = ab.DefineDynamicModule("TestModule");
TypeBuilder tb = mb.DefineType("TestType");
MethodBuilder method = tb.DefineMethod("Ldftn",MethodAttributes.Public | MethodAttributes.Static, typeof(Action<Base>), Type.EmptyTypes);
var ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldnull);
ilgen.Emit(OpCodes.Ldftn, typeof(Base).GetMethod("Method"));
ilgen.Emit(OpCodes.Newobj, typeof(Action<Base>).GetConstructors()[0]);
ilgen.Emit(OpCodes.Ret);
method = tb.DefineMethod("Ldvirtftn", MethodAttributes.Public | MethodAttributes.Static, typeof(Action<Base>), Type.EmptyTypes);
ilgen = method.GetILGenerator();
ilgen.Emit(OpCodes.Ldnull);
ilgen.Emit(OpCodes.Ldvirtftn, typeof(Base).GetMethod("Method"));
ilgen.Emit(OpCodes.Newobj, typeof(Action<Base>).GetConstructors()[0]);
ilgen.Emit(OpCodes.Ret);
var type = tb.CreateType();
var func = Delegate.CreateDelegate(typeof(Func<Action<Base>>),tb.GetMethod("Ldftn")) as Func<Action<Base>>;
var func2 = Delegate.CreateDelegate(typeof(Func<Action<Base>>), tb.GetMethod("Ldvirtftn")) as Func<Action<Base>>;
func()(new Child());
func2()(new Child());
}
}
答案 0 :(得分:8)
以下是ldftn
案例中发生的情况。您的方法创建一个具有以下内容的委托:
Base.Method()
作为方法(不静态)。您将此委托创建为Action<Base>
,恰好有一个参数。当您在此行中调用此委托时:
func()(new Child());
CLR使用新的Child
实例作为“第一个参数”。由于您调用的方法是非静态,因此第一个参数变为this
指针。因此,此调用等同于
new Child().Method();
这会导致在调用时单独的虚拟方法调度 (而不是在ldftn时间),因此调用Child.Method()
。这就是为什么它打印“Child”而不是你可能期望的“Base”。
在ldvirtftn
的情况下,您收到的程序无效,因为您忘记ldvirtftn
需要堆栈上的对象引用而ldftn
没有。
您可以尝试进行以下更改以了解正在发生的事情:
而不是null
,将Base
或Child
的实际实例传递给委托构造函数,这是非静态方法的惯例。您会发现它将拒绝创建委托,因为参数的数量不再匹配(Action<Base>
需要一个参数,但Method()
没有)。
通过将Action<Base>
更改为Action
或使Method()
接受参数,使参数数量匹配。在这两种情况下,您可能会很快发现它符合您的期望。特别是,您会发现使用ldftn
创建的委托将始终致电Base.Method()
,即使您使用Child
的实例创建了该委托。