使用 Reflection.Emit 为给定属性列表生成接口实现

Generate interface implementation with Reflection.Emit for List of given properties

我正在使用 this question 中的代码从 属性

的列表中生成 class
public class Field
{
    public string FieldName;
    public Type FieldType;
}

public static class MyTypeBuilder
    {
        public static Type CompileResultType()
        {
            TypeBuilder tb = GetTypeBuilder();
            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
            foreach (var field in yourListOfFields)
                CreateProperty(tb, field.FieldName, field.FieldType);

            Type objectType = tb.CreateType();
            return objectType;
        }

        private static TypeBuilder GetTypeBuilder()
        {
            var typeSignature = "MyDynamicType";
            var an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }

我有接口到 get/set 它是避免使用反射和动态的属性

public interface IDynamicObject
{
    T GetProperty<T>(string propertyName);
    void SetProperty(string propertyName, object value);
}

任何人都可以帮我修改此代码以生成实现我的 IDynamicObject 接口的 class,以便它生成类似这样的内容(例如两个字符串属性“Str1”和“Str2”)吗?遗憾的是,我对 Reflection.Emit 还不太满意...

public class TestClass : IDynamicObject
{
    public string Str1 { get; set; }
    public string Str2 { get; set; }

    public T GetProperty<T>(string propertyName)
    {
        switch (propertyName)
        {
            case nameof(Str1):
                return CastObject<T>(Str1);

            case nameof(Str2):
                return CastObject<T>(Str2);

            default: throw new ArgumentException();
        }
        
    }

    public void SetProperty(string propertyName, object value)
    {
        switch (propertyName)
        {
            case nameof(Str1):
                Str1 = (string)value;
                break;

            case nameof(Str2):
                Str2 = (string)value;
                break;

            default: throw new ArgumentException();
        }
    }

    public T CastObject<T>(object input)
    {
        return (T)input;
    }
}

这是您可以使用的一种方法。这有点复杂,因为我们要用标签和 goto 逻辑

来实现它

在下面的代码中查看我的评论。

public static class MyTypeBuilder
{
    public static void CompileAssembly(Field[] fields)
    {
        const string typeSignature = "MyDynamicType";

        var assemblyName = new AssemblyName(typeSignature);
        var assemblyBuilder =
            AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule", $"{typeSignature}.dll");

        CompileResultType(moduleBuilder, typeof(IDynamicObject), fields);

        // save to review the compiled assembly
        assemblyBuilder.Save($"{typeSignature}.dll");
    }

    private static Type CompileResultType(ModuleBuilder moduleBuilder, Type interfaceType, Field[] fields)
    {
        var typeBuilder = moduleBuilder.DefineType(moduleBuilder.Assembly.GetName().Name,
            TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
            null, new[] { interfaceType });

        // T CastObject<T>(object input)
        var castObject = typeBuilder.DefineMethod("CastObject",
            MethodAttributes.Public,
            null, new[] { typeof(object) });
        {
            var castObjectOutputParameter = castObject.DefineGenericParameters("T")[0];
            castObject.SetReturnType(castObjectOutputParameter);
            castObject.DefineParameter(1, ParameterAttributes.None, "input");

            var il = castObject.GetILGenerator();

            il.DeclareLocal(castObjectOutputParameter);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ret);
        }

        // create properties in advance so we can reference them later
        var properties = fields.ToDictionary(
            key => key.FieldName,
            value => CreateProperty(typeBuilder, value.FieldName, value.FieldType)
        );

        // T GetProperty<T>(string propertyName)
        var getProperty = typeBuilder.DefineMethod("GetProperty",
            MethodAttributes.Public | MethodAttributes.Final |
            MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
            null, new[] { typeof(string) });
        {
            // define generic parameter T
            var outputParameter = getProperty.DefineGenericParameters("T")[0];
            getProperty.SetReturnType(outputParameter);

            // define name for the propertyName parameter
            getProperty.DefineParameter(1, ParameterAttributes.None, "propertyName");

            // reference to "operator ==" to compare a field name with the propertyName value
            var stringEquals =
                typeof(string).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public) ??
                throw new InvalidOperationException();

            var il = getProperty.GetILGenerator();

            // declare local variables
            il.DeclareLocal(typeof(string)); // loc_0 to store input for switch / case
            il.DeclareLocal(outputParameter); // loc_1 to store result of the CastObject() call

            // define general labels, we will mark their code locations later on
            var returnLabel = il.DefineLabel(); // "return label"
            var throwLabel = il.DefineLabel(); // "throw label" for throwing ArgumentException

            // define "value labels" for each case body,
            // use map to reference them later
            var returnValueLabels = fields.ToDictionary(
                key => key.FieldName,
                value => il.DefineLabel()
            );

            // store propertyName in loc_0
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stloc_0);

            foreach (var field in fields)
            {
                // check if propertyName == field.FieldName
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, field.FieldName);
                il.Emit(OpCodes.Call, stringEquals);

                // if true, jump to the corresponding "return value" label
                // we will mark a code location with it later (see next loop),
                // right now we only need a reference
                il.Emit(OpCodes.Brtrue, returnValueLabels[field.FieldName]);
            }

            // if we are here, that means the propertyName is unknown
            // jump to the "throw label" location
            il.Emit(OpCodes.Br, throwLabel);

            foreach (var field in fields)
            {
                // mark the code with the corresponding "return value" label
                il.MarkLabel(returnValueLabels[field.FieldName]);

                // find a property we created before
                // and pass its getter to the CastObject<T>() call 
                var property = properties[field.FieldName];
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, property.GetMethod);
                il.Emit(OpCodes.Call, castObject);

                // store result in loc_1
                il.Emit(OpCodes.Stloc_1);

                // jump to "return label"
                il.Emit(OpCodes.Br, returnLabel);
            }

            // mark the following code that throws with "throw label" 
            il.MarkLabel(throwLabel);
            // find ArgumentException(string) ctor
            var argumentException =
                typeof(ArgumentException).GetConstructor(new[] { typeof(string) }) ??
                throw new InvalidOperationException();
            // construct exception and throw
            il.Emit(OpCodes.Ldstr, "propertyName");
            il.Emit(OpCodes.Newobj, argumentException);
            il.Emit(OpCodes.Throw);

            // mark the following code with "return label" 
            il.MarkLabel(returnLabel);
            // load value from loc_1
            il.Emit(OpCodes.Ldloc_1);
            // return
            il.Emit(OpCodes.Ret);
        }

        // void SetProperty(string propertyName, object value)
        // logic is very similar to GetProperty
        var setProperty = typeBuilder.DefineMethod("SetProperty",
            MethodAttributes.Public | MethodAttributes.Final |
            MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
            null, new[] { typeof(string), typeof(object) });
        {
            setProperty.DefineParameter(1, ParameterAttributes.None, "propertyName");
            setProperty.DefineParameter(2, ParameterAttributes.None, "value");

            var stringEquals =
                typeof(string).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public) ??
                throw new InvalidOperationException();

            var il = setProperty.GetILGenerator();
            il.DeclareLocal(typeof(string));
            il.DeclareLocal(typeof(string));

            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stloc_0);

            var returnLabel = il.DefineLabel();
            var throwLabel = il.DefineLabel();
            var setValueLabels = fields.ToDictionary(
                key => key.FieldName,
                value => il.DefineLabel()
            );

            foreach (var field in fields)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, field.FieldName);
                il.Emit(OpCodes.Call, stringEquals);
                il.Emit(OpCodes.Brtrue, setValueLabels[field.FieldName]);
            }

            il.Emit(OpCodes.Br, throwLabel);

            foreach (var field in fields)
            {
                var property = properties[field.FieldName];
                il.MarkLabel(setValueLabels[field.FieldName]);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_2);
                il.Emit(OpCodes.Castclass, field.FieldType);
                il.Emit(OpCodes.Call, property.SetMethod);
                il.Emit(OpCodes.Br, returnLabel);
            }

            il.MarkLabel(throwLabel);
            var argumentException =
                typeof(ArgumentException).GetConstructor(new[] { typeof(string) }) ??
                throw new InvalidOperationException();

            il.Emit(OpCodes.Ldstr, "propertyName");
            il.Emit(OpCodes.Newobj, argumentException);
            il.Emit(OpCodes.Throw);

            il.MarkLabel(returnLabel);
            il.Emit(OpCodes.Ret);
        }

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

        return typeBuilder.CreateType();
    }

    private static PropertyBuilder CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
    {
        FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
    
        PropertyBuilder propertyBuilder =
            tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName,
            MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType,
            Type.EmptyTypes);
        ILGenerator getIl = getPropMthdBldr.GetILGenerator();
    
        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, fieldBuilder);
        getIl.Emit(OpCodes.Ret);
    
        MethodBuilder setPropMthdBldr =
            tb.DefineMethod("set_" + propertyName,
                MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.HideBySig,
                null, new[] { propertyType });
    
        ILGenerator setIl = setPropMthdBldr.GetILGenerator();
        Label modifyProperty = setIl.DefineLabel();
        Label exitSet = setIl.DefineLabel();
    
        setIl.MarkLabel(modifyProperty);
        setIl.Emit(OpCodes.Ldarg_0);
        setIl.Emit(OpCodes.Ldarg_1);
        setIl.Emit(OpCodes.Stfld, fieldBuilder);
    
        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSet);
        setIl.Emit(OpCodes.Ret);
    
        propertyBuilder.SetGetMethod(getPropMthdBldr);
        propertyBuilder.SetSetMethod(setPropMthdBldr);

        return propertyBuilder;
    }
}

这是它的工作原理

var fields = new[]
{
    new Field { FieldName = "Str1", FieldType = typeof(string) },
    new Field { FieldName = "Str2", FieldType = typeof(string) },
    new Field { FieldName = "Str3", FieldType = typeof(string) }
};
MyTypeBuilder.CompileAssembly(fields);

...将生成以下内容:

public class MyDynamicType : IDynamicObject
{
    // ... getters and setters

    public T CastObject<T>(object input)
    {
        return (T)input;
    }

    public T GetProperty<T>(string propertyName)
    {
        switch (propertyName)
        {
        case "Str1":
            return CastObject<T>(this.Str1);
        case "Str2":
            return CastObject<T>(this.Str2);
        case "Str3":
            return CastObject<T>(this.Str3);
        default:
            throw new ArgumentException("propertyName");
        }
    }

    public void SetProperty(string propertyName, object value)
    {
        switch (propertyName)
        {
        case "Str1":
            Str1 = (string)value;
            break;
        case "Str2":
            Str2 = (string)value;
            break;
        case "Str3":
            Str3 = (string)value;
            break;
        default:
            throw new ArgumentException("propertyName");
        }
    }
}