Mic*_*l B 3 .net c# il reflection.emit
以下示例程序是我试图掌握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());
}
}
Run Code Online (Sandbox Code Playgroud)
这是ldftn案件中发生的事情.您的方法创建一个具有以下内容的委托:
Base.Method()作为方法(不是静态的).您创建此委托Action<Base>,恰好有一个参数.当您在此行中调用此委托时:
func()(new Child());
Run Code Online (Sandbox Code Playgroud)
CLR使用新Child实例作为"第一个参数".由于您调用的方法不是静态的,因此第一个参数成为this指针.因此,这个呼叫变得等同于
new Child().Method();
Run Code Online (Sandbox Code Playgroud)
这会在调用时(不是在ldftn时间)导致单独的虚方法调度,因此Child.Method()被调用.这就是它打印"Child"而不是你可能期望的"Base"的原因.
在这种ldvirtftn情况下,您将获得一个无效的程序,因为您忘记了ldvirtftn需要在堆栈上的对象引用而ldftn不是.
您可以尝试进行以下更改以了解发生了什么:
相反的null,通过一个实际的实例Base或Child到委托的构造函数,如通常用于非静态方法.您会发现它将拒绝创建委托,因为参数的数量不再匹配(Action<Base>需要一个参数,但Method()没有参数).
通过Action<Base>简单地更改Action或通过Method()接受参数来使参数的数量匹配.在这两种情况下,您可能会很快发现它符合您的期望.特别是,您会发现创建的委托ldftn将始终调用,Base.Method()即使您使用实例创建它也是如此Child.