簡體   English   中英

System.Reflection.Emit:將類和接口綁定在一起

[英]System.Reflection.Emit: bind together a class and an interface

我想將A類和接口B綁定在一起並創建一個C類,以便C實現接口B 也:

  • 類型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;
    }
} 

這就是我所做的(通常我的意思是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);
    }
}

我得到的例外:

程序集'DynamicAseembly123,Version = 0.0.0.0,Culture = neutral,PublicKeyToken = null'類型'DynamicType123'中的方法'get_Name'沒有實現。

感謝您提供任何幫助或提示。


更新 :我使用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

我使用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);
    }
}

原始問題是因為getter / setter方法需要MethodAttributes.Virtual才能能夠隱式實現相應的接口方法。

添加該標志足以實現所需的隱式接口實現。 對於顯式接口實現,你必須使用DefineMethodOverride如在另一篇文章中提到,但MethodAttributes.Virtual仍然是必需的。 實際上你也可以使用DefineMethodOverride和隱式實現 - 它不會受到傷害,但是不需要。

以上內容已在您更新的代碼中修復。 但現在它生成了InvalidProgramException 這是由於(復制/粘貼我猜)在set方法體生成中使用get IL生成器變量引起的:

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);

這當然會為getter和setter發出無效代碼。 使用正確的變量,一切都會好的。 基本上

var setIl = setPropMthdBldr.GetILGenerator();
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldfld, _entityFieldBldr);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Callvirt, setterMethodInfo);
setIl.Emit(OpCodes.Ret);

暫無
暫無

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

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