【问题标题】:Generate interface implementation with Reflection.Emit for List of given properties使用 Reflection.Emit 为给定属性列表生成接口实现
【发布时间】:2021-11-20 15:29:11
【问题描述】:

我正在使用来自this question 的代码从属性列表中生成类

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

我有接口来获取/设置它的属性以避免使用反射和动态

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

谁能帮我修改这段代码以生成一个实现我的 IDynamicObject 接口的类,以便生成类似这样的东西(例如两个字符串属性“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;
    }
}

【问题讨论】:

    标签: c# reflection reflection.emit


    【解决方案1】:

    这是您可以做到的一种方法。这有点复杂,因为我们要用标签和goto 逻辑来实现它?

    在下面的代码中查看我的 cmets。

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

    【讨论】:

    • 谢谢!在我替换了行“var property = CreateProperty(typeBuilder, field.FieldName, field.FieldType);”之后使用“var 属性 = 属性 [field.FieldName];”因为它两次生成每个属性,所以它对 1-8 个属性有效,但是如果我传递 9 个或更多属性,它会抛出错误“System.NotSupportedException: 'Illegal one-byte branch at position: 79. Requested branch was: 129. '"
    • 哎哟?‍♂️。当然是!谢谢!已在答案中修复。
    • 为了解决“Illegal one-byte branch at position”错误,我将 Brtrue_S 替换为 Brtrue 并将 Br_S 替换为 Br,就像这个问题中所说的 stackoverflow.com/questions/3469710/…
    • @DrWh0 谢谢!在答案中反映了修复
    • 这是一个很好的问题。据我了解,从 IL 的角度来看,有 only one "true" switch instruction,它仅用于处理 int 参数。具有非整数 switch 的代码将编译为与上述类似的 IL。一些工具(例如ILSpy)仍然将其反编译为switch 语句。所以也许这是一个“视角问题”的事情。但我不是这方面的专家;如果还没有这样的讨论,也许它值得单独讨论。
    猜你喜欢
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 2020-06-25
    • 2015-02-03
    相关资源
    最近更新 更多