如何使用System.Reflection.Emit

时间:2018-02-11 15:59:13

标签: c# generics system.reflection

我理解困难的是:我应该在构建方法中做些什么?

我们的目的是实现一个框架,该框架具有一个方法,该方法生成一个域类的新实例,其中的属性和方法添加了功能。 以下示例介绍了创建域Stock对象的情况,其中传递给build的参数将作为参数传递给它实例化中的stock的构造函数。

  

Enhancer.Build<Stock>("Apple", "Dow Jones");

添加的功能由标记虚拟方法和属性的自定义属性指定,如以下示例所示。在注释中说明我们打算使用功能Build of Enhancer从返回的对象中开始验证标记的方法和属性。 示例中的每个自定义属性都应具有公共基本类型 - EnhancedAtributte - 使用抽象方法Check(object [] args)从标记方法接收参数。

class Stock
 {
 public Stock(string name, string index) { ... }
 [NonNull]
 public virtual string Market { get; set; } // set will launch an exception if values are null
 [Min(73)]
 public virtual long Quote { get; set; } // set will launch an exception if values are < 73
 [Min(0.325)]
 public virtual double Rate { get; set; } // set will launch an exception if values are < 0.325
 [Accept("Jenny", "Lily", "Valery")]
 public virtual string Trader{get; set; } // set will launch an exception if values are different than Jenny, Lily or Valery 
 [Max(58)]
 public virtual int Price { get; set; } // set will launch an exception if values are > 58
 // it will launch exception if the state of this or any of the parameters has been altered by the execution of a marked method
 // -- BuildInterest
 [NoEffects]
 public double BuildInterest(Portfolio port, Store st) { ... }
 }
  • 实施策略:
    • 项目Enhancer必须使用System.Reflection.Emit API。
      • 不得使用给定的示例来破解解决方案。
      • 一个函数Build(params object [] args)应该返回一个从T派生的新类的新实例,让它称之为T', 重新定义了T的虚拟方法。
      • 新的类T'是使用API​​资源System.Reflection.Emit。
      • 动态创建的
      • 对于T中标记的每个M方法(自定义属性),应该在类T'中创建方法M'的新定义。 该 方法M'应该调用基本方法M并且应该执行指定的方法 通过自定义属性。

1 个答案:

答案 0 :(得分:0)

我首先要验证T既不是封闭的也不是抽象的。这应该足以确保它是(1)一个类; (2)能够延长。

接下来,迭代typeof(T).GetProperties()以查找CanWritetrueproperty.GetCustomAttributes<EnhancedAtributte>().Any()报告true的所有“增强”属性。如果没有任何匹配的属性,您可以直接实例化T

由于属性实例本身验证了属性值,因此您需要在某处缓存属性,以免在每次属性更改时产生昂贵的查找。你应该瞄准生成一个看起来像这样的类:

public class __Enhanced__Stock__ {
    private static readonly EnhancedAttribute[] __Price__Attributes__;

    static __Enhanced__Stock__() {
        __Price__Attributes__ = typeof(Stock).GetProperty("Price")
                                            .GetCustomAttributes<EnhancedAtributte>()
                                            .ToArray();
    }

    public override int Price {
        get => base.Price;
        set {
            for (int i = 0, n = __Price__Attributes__.Length; i < n; i++) 
                __Price__Attributes__[i].Check(new Object[] { (Object)value });
            }
            base.Price = value;
        }
    }
}

可以从TypeBuilder创建ModuleBuilderAssemblyBuilder是从TypeBuilder.DefineField创建的。对于后两者,你可以保留一个静态实例。

您需要使用EnhancedAttribute[][]定义静态字段,以用作每个属性的属性缓存(或使用属性索引的单个TypeBuilder.DefineTypeInitializer)。在任何一种情况下,您都必须定义一个类构造函数(请参阅MethodBuilder.GetILGenerator())来初始化缓存。您必须使用TypeBuilder.DefineProperty自己编写IL。

对于您找到的每个增强属性,您需要定义一个具有相同名称的新属性(请参阅get),并为每个属性发出单独的setTypeBuilder.DefineMethod访问者(见PropertyBuilder.SetGetMethod)。每个访问者都需要绑定到属性,这可以通过SetSetMethodT来完成。您还必须在TypeBuilder.DefineMethodOverride中使访问者覆盖访问者,您可以通过get执行访问。您可以在覆盖属性in this question上看到一些提示。

set访问器覆盖的代码很简单:只需要​​委托给base属性。 Check访问器更复杂,因为您需要遍历属性的属性缓存并调用每个属性的for方法。同样,您需要手动发出IL,其中包括确定如何发出简单的Check循环。或者,由于您已经知道每个属性的属性数,因此您可以编写一个手动展开的循环。无论如何,对于每次拨打object[],请记住您需要初始化一个新的value,其中必须复制CreateType()参数。

一旦声明了属性缓存字段,类型初始值设定项,属性及其访问器,就基本完成了。您可以通过调用TypeBuilder上的public class Enhancer { private static readonly ModuleBuilder ModuleBuilder; static Enhancer() { var b = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run); ModuleBuilder = b.DefineDynamicModule($"{b.GetName().Name}.Module"); } private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const FieldAttributes CacheFlags = FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly; private const TypeAttributes TypeFlags = TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass; private static IEnumerable<PropertyInfo> FindEnhancedProperties(Type t) { foreach (var p in t.GetProperties(InstanceFlags)) { if (p.CanWrite && p.GetSetMethod(true).IsVirtual && p.IsDefined(typeof(EnhancedAttribute))) { yield return p; } } } public static EnhancedAttribute[] FindEnhancedAttributes(PropertyInfo p) { return p.GetCustomAttributes<EnhancedAttribute>().ToArray(); } private static readonly MethodInfo CheckMethod = typeof(EnhancedAttribute).GetMethod( nameof(EnhancedAttribute.Check), new[] { typeof(object[]) }); private static readonly MethodInfo GetTypeFromHandleMethod = typeof(Type).GetMethod( nameof(Type.GetTypeFromHandle), new[] { typeof(RuntimeTypeHandle) }); private static readonly MethodInfo GetPropertyMethod = typeof(Type).GetMethod( nameof(Type.GetProperty), new[] { typeof(string), typeof(BindingFlags) }); private static readonly MethodInfo FindEnhancedAttributesMethod = typeof(Enhancer).GetMethod( nameof(FindEnhancedAttributes), new[] { typeof(PropertyInfo) }); private readonly Type _base; private readonly TypeBuilder _enhanced; private readonly PropertyInfo[] _properties; private readonly FieldBuilder[] _attributeCaches; private readonly MethodBuilder[] _propertySetters; private static readonly Dictionary<Type, Type> Cache = new Dictionary<Type, Type>(); public static T Build<T>(params object[] args) where T : class { Type type; lock (Cache) { if (!Cache.TryGetValue(typeof(T), out type)) Cache[typeof(T)] = type = new Enhancer(typeof(T)).Enhance(); } return (T)Activator.CreateInstance(type, args); } private Enhancer(Type t) { if (t?.IsSealed != false || t.IsInterface) { throw new ArgumentException( "Type must be a non-sealed, non-abstract class type."); } _base = t; _enhanced = ModuleBuilder.DefineType($"<Enhanced>{t.FullName}", TypeFlags, t); _properties = FindEnhancedProperties(t).ToArray(); _attributeCaches = _properties.Select( p => _enhanced.DefineField( $"__{p.Name}Attributes", typeof(EnhancedAttribute[]), CacheFlags)).ToArray(); _propertySetters = new MethodBuilder[_properties.Length]; } private Type Enhance() { GenerateTypeInitializer(); for (int i = 0, n = _properties.Length; i < n; i++) EnhanceProperty(i); GenerateConstructors(); return _enhanced.CreateType(); } private void GenerateConstructors() { var baseCtors = _base.GetConstructors(InstanceFlags); foreach (var baseCtor in baseCtors) { if (baseCtor.IsPrivate) continue; var parameters = baseCtor.GetParameters(); var ctor = _enhanced.DefineConstructor( baseCtor.Attributes, baseCtor.CallingConvention, parameters.Select(p => p.ParameterType).ToArray()); var il = ctor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); for (int i = 0; i < parameters.Length; i++) il.Emit(OpCodes.Ldarg, i + 1); il.Emit(OpCodes.Call, baseCtor); il.Emit(OpCodes.Ret); } } private void GenerateTypeInitializer() { var typeInit = _enhanced.DefineTypeInitializer(); var il = typeInit.GetILGenerator(); for (int i = 0, n = _properties.Length; i < n; i++) { var p = _properties[i]; il.Emit(OpCodes.Ldtoken, _base); il.EmitCall(OpCodes.Call, GetTypeFromHandleMethod, null); il.Emit(OpCodes.Ldstr, p.Name); il.Emit(OpCodes.Ldc_I4_S, (int)InstanceFlags); il.EmitCall(OpCodes.Call, GetPropertyMethod, null); il.EmitCall(OpCodes.Call, FindEnhancedAttributesMethod, null); il.Emit(OpCodes.Stsfld, _attributeCaches[i]); } il.Emit(OpCodes.Ret); } private void EnhanceProperty(int index) { var p = _properties[index]; var property = _enhanced.DefineProperty( p.Name, p.Attributes, p.PropertyType, null); var baseSet = p.GetSetMethod(true); var set = _enhanced.DefineMethod( baseSet.Name, baseSet.Attributes & ~MethodAttributes.NewSlot | MethodAttributes.Final, baseSet.CallingConvention, baseSet.ReturnType, new[] { p.PropertyType }); property.SetSetMethod(set); _enhanced.DefineMethodOverride(set, baseSet); var il = set.GetILGenerator(); var attributeCount = p.GetCustomAttributes<EnhancedAttribute>().Count(); for (int j = 0; j < attributeCount; j++) { il.Emit(OpCodes.Ldsfld, _attributeCaches[index]); il.Emit(OpCodes.Ldc_I4, j); il.Emit(OpCodes.Ldelem_Ref, j); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Newarr, typeof(object)); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldarg_1); if (p.PropertyType.IsValueType) il.Emit(OpCodes.Box, p.PropertyType); il.Emit(OpCodes.Stelem_Ref); il.EmitCall(OpCodes.Callvirt, CheckMethod, null); } il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.EmitCall(OpCodes.Call, baseSet, null); il.Emit(OpCodes.Ret); _propertySetters[index] = set; } } 来“烘焙”派生类型。

部分解决方案

我觉得要写一些代码,所以这是一个应该处理基于属性的属性验证的解决方案。我并不完全清楚其他方法的属性应如何工作,但这应该是一个很好的起点。

array_combine