簡體   English   中英

使用 Reflection.Emit 覆蓋一個方法

[英]Use Reflection.Emit to override a method

我正在使用System.Reflection.Emit為抽象基礎 class 生成代理類型的工具。 現在的最終要求是能夠通過提供Func來覆蓋虛擬/抽象方法,並允許代理調用該Func保留原始調用的 object 上下文。 例如,給定一個簡單的類型,如:

public class A
{
    public string Name { get; set; }
    public virtual int NameLen() => 42;
}

我希望能夠提供一個Func<A, int>來覆蓋NameLen 我能夠很容易地生成基本代理,但我不能完全使覆蓋工作。 這是一個最小的示例,代理上面的 class:

static Type GetProxyFor(Type type, Func<A, int> oride, string methodName)
{
    // basic type setup
    var assmName = new AssemblyName("AproxyAssm");
    var assmBuilder = AssemblyBuilder.DefineDynamicAssembly(assmName, AssemblyBuilderAccess.Run);
    var moduleBuilder = assmBuilder.DefineDynamicModule(assmName.Name);
    var typeBuilder = moduleBuilder.DefineType("AProxy", TypeAttributes.Public, type);

    // create the override method
    var mInfo = typeBuilder.BaseType.GetMethod(methodName);
    var mBody = typeBuilder.DefineMethod(mInfo.Name,
        MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig,
        mInfo.ReturnType, null);

    // call the Func 
    var ilGen = mBody.GetILGenerator();
    ilGen.Emit(OpCodes.Nop);
    ilGen.Emit(OpCodes.Ldsfld, oride.Target.GetType().GetFields()[0]); // ???
    ilGen.Emit(OpCodes.Ldarg_0); // load method obj context as func arg 1
    ilGen.Emit(OpCodes.Callvirt, oride.Method);
    ilGen.Emit(OpCodes.Ret);

    typeBuilder.CreateTypeInfo();
    var rettype = typeBuilder.CreateType();

    return rettype;
}

生成的 IL 似乎是正確的,因為我沒有收到任何 JIT 錯誤或舊的最喜歡的CLR detected an invalid program ,並且代理 class 以其他方式工作,即使對於更復雜的示例也是如此。 只要,就是因為我不調用被覆蓋的方法。 當我調用該方法時,我得到的是System.FieldAccessException: Attempt by method 'AProxy.NameLen()' to access field 'EmitTests.OverrideTests+<>c.<>9' failed. ' failed. System.FieldAccessException: Attempt by method 'AProxy.NameLen()' to access field 'EmitTests.OverrideTests+<>c.<>9' failed. ' failed.

結合以上所有內容的單元測試:

[Fact]
public void CanEmitOverrideMethod()
{
    Func<A, int> f = (t) => t.Name.Length;

    var t = GetProxyFor(typeof(A), f, "NameLen");

    var obj = Activator.CreateInstance(t);

    Assert.IsAssignableFrom<A>(obj);
    ((A)obj).Name = "Foo";

    Assert.Equal(3, ((A)obj).NameLen());
}

更新了更簡潔的示例。 此外,在檢查生成的<>c class 時,這里是來自 ILSpy 的 output:

// EmitTests.OverrideTests.<>c
using System;
using System.Runtime.CompilerServices;

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
    public static readonly <>c <>9 = new <>c();

    public static Func<A, int> <>9__1_0;

    internal int <CanEmitOverrideMethod>b__1_0(A t)
    {
        return t.Name.Length;
    }
}

對我來說,這一切看起來都不錯,但我有點不知道接下來去哪里 go。

這個答案並不像我使用Emit覆蓋的目標那樣聰明或有趣,但它 a) 有效並且 b) 可能更易於維護。 創建代理類型時,我使用 static 字典注冊 func,然后在運行時進行查找。 一些快速而骯臟的基准測試表明查找基本上是免費的。 調用沒有注冊函數的實例與調用超過 100 萬次重復的常規方法一樣快。 注冊一個 func 會有一點點開銷,但在相同的 1m 次重復中,它只會增加幾分之一秒。 開銷似乎是在實際調用 Func 時,它可能也存在於上面的 Emit() 方法中。

public class A
{
    public string Name { get; set; }
    public virtual int NameLen()
    {
        var t = this.GetType();
        if (funcs.ContainsKey(t))
        {
            return funcs[t](this);
        }
        else
        {
            return 42;
        }
    }

    public static Dictionary<Type, Func<A, int>> funcs = new Dictionary<Type, Func<A, int>>();
}

和代理生成器:

static Type GetProxyFor<T>(Func<A, int> oride) where T : A
{
    // basic type setup
    var assmName = new AssemblyName("AproxyAssm");
    var assmBuilder = AssemblyBuilder.DefineDynamicAssembly(assmName, AssemblyBuilderAccess.Run);
    var moduleBuilder = assmBuilder.DefineDynamicModule(assmName.Name);
    var typeBuilder = moduleBuilder.DefineType("AProxy", TypeAttributes.Public, typeof(T));

    typeBuilder.CreateTypeInfo();
    var rettype = typeBuilder.CreateType();

    if (oride != null)
    {
        A.funcs.Add(rettype, oride);
    }

    return rettype;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM