System.Reflection.Emit:将类和接口绑定在一起

Nod*_*.JS 5 c# .net-core

我想一个类绑定在一起,A和接口B,并创建一个类型C,从而C实现接口B。也:

  • type C有一个构造函数,该构造函数将类型的对象A作为构造函数,我们称之为i
  • 和给定的从地图上的所有属性的AB(假设在地图上的所有属性具有相同的类型),那么它使用从属性值i来代替。

例如:

class A { public string Name { get; set; } } 

interface B { string Name { get; set; } } 

class C : B {
    private readonly A _i;

    public C(A i) { 
        _i = i;
    }

    public string Name
    {
        get => _i.Name;
        set => _i.Name = value;
    }
} 
Run Code Online (Sandbox Code Playgroud)

这就是我所做的(通常,我指的B是源代码A):

/// <summary>
/// Creates a new type dynamically
/// </summary>
public class CustomTypeGenerator<TSource, TCommon>
{
    private readonly TypeBuilder _tb;

    private readonly FieldBuilder _entityFieldBldr;

    private readonly Type _srcType;

    /// <summary>
    /// Initialize custom type builder
    /// </summary>
    public CustomTypeGenerator(IEnumerable<(string CommonPrpName, Type Type, string SourcePrpName)> members)
    {
        var cmType = typeof(TCommon);
        _srcType = typeof(TSource);

        if (!cmType.IsInterface)
        {
            throw new Exception("Type has to be an interface");
        }

        const string assemblyName = "DynamicAseembly123";
        const string typeSignature = "DynamicType123";

        var assemblyBuilder =
            AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module123");

        _tb = moduleBuilder.DefineType(typeSignature,
            TypeAttributes.Public |
            TypeAttributes.Class |
            TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass |
            TypeAttributes.BeforeFieldInit |
            TypeAttributes.AutoLayout);

        _tb.AddInterfaceImplementation(cmType);

        _entityFieldBldr = EmitSourceField();

        _tb.DefineDefaultConstructor(
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName);

        var constructorBuilder = _tb.DefineConstructor(MethodAttributes.Public,
            CallingConventions.Standard,
            new[] {_srcType});

        constructorBuilder.DefineParameter(0, ParameterAttributes.None, "entity");

        var constructorIl = constructorBuilder.GetILGenerator();
        constructorIl.Emit(OpCodes.Ldarg_0);
        constructorIl.Emit(OpCodes.Ldarg_1);
        constructorIl.Emit(OpCodes.Stfld, _entityFieldBldr);
        constructorIl.Emit(OpCodes.Ret);

        foreach (var (commonPrpName, type, sourcePrpName) in members)
        {
            EmitProperty(commonPrpName, type, sourcePrpName);
        }

        EmittedType = _tb.CreateType();
    }

    public Type EmittedType { get; }

    private FieldBuilder EmitSourceField()
    {
        var entityBldr = _tb.DefineField("_" + "entity", _srcType, FieldAttributes.Private);

        return entityBldr;
    }

    private void EmitProperty(string cPn, Type cmPt, string sPn)
    {
        var srcProp = _srcType.GetProperty(sPn, BindingFlags.Public | BindingFlags.Instance);
        var propertyBldr = _tb.DefineProperty(cPn, PropertyAttributes.HasDefault, cmPt, null);

        var getterMethodInfo = srcProp.GetMethod ?? throw new Exception("Missing getter!");
        var getPropMthdBldr = _tb.DefineMethod($"get_{cPn}",
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            cmPt, Type.EmptyTypes);

        var getIl = getPropMthdBldr.GetILGenerator();
        var getProperty = getIl.DefineLabel();
        var exitGet = getIl.DefineLabel();

        getIl.MarkLabel(getProperty);
        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, _entityFieldBldr);
        getIl.Emit(OpCodes.Call, getterMethodInfo);
        getIl.Emit(OpCodes.Dup);
        getIl.MarkLabel(exitGet);
        getIl.Emit(OpCodes.Ret);

        var setterMethodInfo = srcProp.SetMethod ?? throw new Exception("Missing setter!");
        var setPropMthdBldr = _tb.DefineMethod($"set_{cPn}",
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            null, new[] {cmPt});

        var setIl = setPropMthdBldr.GetILGenerator();
        var modifyProperty = setIl.DefineLabel();
        var exitSet = setIl.DefineLabel();

        setIl.MarkLabel(modifyProperty);
        setIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, _entityFieldBldr);
        setIl.Emit(OpCodes.Ldarg_1);
        getIl.Emit(OpCodes.Call, setterMethodInfo);
        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSet);
        setIl.Emit(OpCodes.Ret);

        propertyBldr.SetGetMethod(getPropMthdBldr);
        propertyBldr.SetSetMethod(setPropMthdBldr);
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到的例外:

程序集“ DynamicAseembly123”中版本“ DynamicType123”中的类型“ DynamicType123”中的方法“ get_Name”,版本= 0.0.0.0,Culture = neutral,PublicKeyToken = null,没有实现。

感谢您的帮助或提示。


更新:我曾经ILSpy在虚拟示例代码上监视生成的IL:

.class private auto ansi beforefieldinit ConsoleApp1.C
    extends [System.Runtime]System.Object
    implements ConsoleApp1.B
{
    // Fields
    .field private initonly class ConsoleApp1.A _i

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            class ConsoleApp1.A i
        ) cil managed 
    {
        // Method begins at RVA 0x206a
        // Code size 16 (0x10)
        .maxstack 8

        // (no C# code)
        IL_0000: ldarg.0
        IL_0001: call instance void [System.Runtime]System.Object::.ctor()
        IL_0006: nop
        IL_0007: nop
        // _i = i;
        IL_0008: ldarg.0
        IL_0009: ldarg.1
        IL_000a: stfld class ConsoleApp1.A ConsoleApp1.C::_i
        // (no C# code)
        IL_000f: ret
    } // end of method C::.ctor

    .method public final hidebysig specialname newslot virtual 
        instance string get_Name () cil managed 
    {
        // Method begins at RVA 0x207b
        // Code size 12 (0xc)
        .maxstack 8

        // return _i.Name;
        IL_0000: ldarg.0
        IL_0001: ldfld class ConsoleApp1.A ConsoleApp1.C::_i
        IL_0006: callvirt instance string ConsoleApp1.A::get_Name()
        // (no C# code)
        IL_000b: ret
    } // end of method C::get_Name

    .method public final hidebysig specialname newslot virtual 
        instance void set_Name (
            string 'value'
        ) cil managed 
    {
        // Method begins at RVA 0x2088
        // Code size 14 (0xe)
        .maxstack 8

        // _i.Name = value;
        IL_0000: ldarg.0
        IL_0001: ldfld class ConsoleApp1.A ConsoleApp1.C::_i
        IL_0006: ldarg.1
        IL_0007: callvirt instance void ConsoleApp1.A::set_Name(string)
        // (no C# code)
        IL_000c: nop
        IL_000d: ret
    } // end of method C::set_Name

    // Properties
    .property instance string Name()
    {
        .get instance string ConsoleApp1.C::get_Name()
        .set instance void ConsoleApp1.C::set_Name(string)
    }

} // end of class ConsoleApp1.C
Run Code Online (Sandbox Code Playgroud)

我使用了提示ILSpy来获取一些提示,这是我更新的C#代码:

/// <summary>
/// Creates a new type dynamically
/// </summary>
public class CustomTypeGenerator<TSource, TCommon>
{
    private readonly TypeBuilder _tb;

    private readonly FieldBuilder _entityFieldBldr;

    private readonly Type _srcType;

    /// <summary>
    /// Initialize custom type builder
    /// </summary>
    public CustomTypeGenerator(Dictionary<string, (Type Type, string SourcePrpName)> members)
    {
        var objType = typeof(object);
        var cmType = typeof(TCommon);
        _srcType = typeof(TSource);

        if (!cmType.IsInterface)
        {
            throw new Exception("Type has to be an interface");
        }

        const string assemblyName = "DynamicAssembly123";
        const string typeSignature = "DynamicType123";

        var assemblyBuilder =
            AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);

        var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module123");

        _tb = moduleBuilder.DefineType(typeSignature,
            TypeAttributes.Public |
            TypeAttributes.Serializable |
            TypeAttributes.Class |
            TypeAttributes.Sealed |
            TypeAttributes.AutoLayout, objType);

        _tb.AddInterfaceImplementation(cmType);

        _entityFieldBldr = EmitSourceField();

        _tb.DefineDefaultConstructor(
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName);

        var constructorBuilder = _tb.DefineConstructor(MethodAttributes.Public,
            CallingConventions.Standard,
            new[] {_srcType});

        constructorBuilder.DefineParameter(0, ParameterAttributes.None, "entity");

        var constructorIl = constructorBuilder.GetILGenerator();
        constructorIl.Emit(OpCodes.Ldarg_0);
        constructorIl.Emit(OpCodes.Ldarg_1);
        constructorIl.Emit(OpCodes.Stfld, _entityFieldBldr);
        constructorIl.Emit(OpCodes.Ret);

        foreach (var (commonPrpName, (type, sourcePrpName)) in members)
        {
            EmitProperty(commonPrpName, type, sourcePrpName);
        }

        EmittedType = _tb.CreateType();
    }

    public Type EmittedType { get; }

    private FieldBuilder EmitSourceField()
    {
        var entityBldr = _tb.DefineField("_" + "entity", _srcType,
            FieldAttributes.Private |
            FieldAttributes.InitOnly);

        return entityBldr;
    }

    private void EmitProperty(string cPn, Type cmPt, string sPn)
    {
        var srcProp = _srcType.GetProperty(sPn, BindingFlags.Public | BindingFlags.Instance);

        var getterMethodInfo = srcProp.GetMethod ?? throw new Exception("Missing getter!");
        var getPropMthdBldr = _tb.DefineMethod($"get_{cPn}",
            MethodAttributes.Public |
            MethodAttributes.Virtual |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            cmPt, Type.EmptyTypes);

        var getIl = getPropMthdBldr.GetILGenerator();
        var getPropertyLbl = getIl.DefineLabel();
        var exitGetLbl = getIl.DefineLabel();

        getIl.MarkLabel(getPropertyLbl);
        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, _entityFieldBldr);
        getIl.Emit(OpCodes.Callvirt, getterMethodInfo);
        getIl.MarkLabel(exitGetLbl);
        getIl.Emit(OpCodes.Ret);

        var setterMethodInfo = srcProp.SetMethod ?? throw new Exception("Missing setter!");
        var setPropMthdBldr = _tb.DefineMethod($"set_{cPn}",
            MethodAttributes.Public |
            MethodAttributes.Virtual |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig,
            null, new[] {cmPt});

        var setIl = setPropMthdBldr.GetILGenerator();
        var modifyPropertyLbl = setIl.DefineLabel();
        var exitSetLbl = setIl.DefineLabel();

        setIl.MarkLabel(modifyPropertyLbl);
        setIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, _entityFieldBldr);
        setIl.Emit(OpCodes.Ldarg_1);
        getIl.Emit(OpCodes.Callvirt, setterMethodInfo);
        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSetLbl);
        setIl.Emit(OpCodes.Ret);

        var propertyBldr = _tb.DefineProperty(cPn, PropertyAttributes.None, cmPt, null);
        propertyBldr.SetGetMethod(getPropMthdBldr);
        propertyBldr.SetSetMethod(setPropMthdBldr);
    }
}
Run Code Online (Sandbox Code Playgroud)