首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Reflection.Emit重写方法

使用Reflection.Emit重写方法
EN

Stack Overflow用户
提问于 2020-12-12 13:54:08
回答 1查看 88关注 0票数 0

我正在开发一个使用System.Reflection.Emit为抽象基类生成代理类型的工具。现在的最后一个要求是能够通过提供Func覆盖虚拟/抽象方法,并允许代理调用该Func,保留原始调用的对象上下文。例如,给定一个简单的类型,如:

代码语言:javascript
复制
public class A
{
    public string Name { get; set; }
    public virtual int NameLen() => 42;
}

我希望能够提供一个覆盖NameLenFunc<A, int>。我可以很容易地生成基本代理,但我不能完全使覆盖工作。下面是一个最小的例子,代理上面的类:

代码语言:javascript
复制
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,代理类的工作方式不同,即使对于更复杂的示例也是如此。只要我不调用被覆盖的方法。当我调用这个方法时,我得到的是System.FieldAccessException : Attempt by method 'AProxy.NameLen()' to access field 'EmitTests.OverrideTests+<>c.<>9' failed. ' failed.

将以上所有内容组合在一起的单元测试:

代码语言:javascript
复制
[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类时,以下是ILSpy的输出:

代码语言:javascript
复制
// 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;
    }
}

对我来说,这一切看起来都是对的,我有点不知道下一步该怎么做。

EN

回答 1

Stack Overflow用户

发布于 2020-12-15 02:12:52

这个答案看起来并不像我用Emit覆盖的那样聪明或有趣,但它a)有效,b)可能更易于维护。在创建代理类型时,我使用静态字典注册func,然后在运行时进行查找。一些快速和肮脏的基准测试表明,查找基本上是免费的;调用没有注册函数的实例的速度与调用常规方法超过100万次的速度一样快。使用func注册会有很小的开销,但在相同的100万个reps上加起来只有零点几秒。开销似乎出现在Func的实际调用中,这可能也存在于上面的Emit()方法中。

代码语言:javascript
复制
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>>();
}

和代理生成器:

代码语言:javascript
复制
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;
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65262060

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档