简体   繁体   English

如何使用 System.Reflection.Emit 从 T 创建派生类型

[英]How can I create a derived type from T, using System.Reflection.Emit

what I am having difficulties understanding is : what should I do in the build method?我难以理解的是:我应该在构建方法中做什么?

It is our intent to implement a framework that has a method that generates a new instance of a class of domain in which their properties and methods have added functionalities.我们的目的是实现一个框架,该框架具有一个方法,该方法生成域类的新实例,其中的属性和方法添加了功能。 The Following example presents a case of the creation of a object of domain Stock where the parameters passed to build will be pass as arguments to the constructor of stock in it's instantiation.下面的示例展示了创建域 Stock 对象的情况,其中传递给 build 的参数将作为参数传递给 Stock 实例化中的构造函数。


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


The added functionalities are specified by custom attributes that mark the virtual methods and properties, as the following example shows.添加的功能由标记虚拟方法和属性的自定义属性指定,如以下示例所示。 In the comments it is said what we intend the marked methods and properties to start verifying in the returned object from the function Build of Enhancer.在评论中说我们打算在从 Enhancer 的函数 Build 返回的对象中开始验证标记的方法和属性。 Every custom attribute in the example should have a common base type - EnhancedAtributte - with an abstract method Check(object[] args) that receives the arguments from the marked method.示例中的每个自定义属性都应该有一个公共基类型 - 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) { ... }
 }
  • Implementation strategies:实施策略:
    • The project Enhancer must use the System.Reflection.Emit API.项目增强器必须使用 System.Reflection.Emit API。
      • The solution must not be compromised with the given examples.解决方案不得与给定的示例妥协。
      • A function Build(params object[] args) should return a new instance of a new class derived from T, let's call it T', that redefines the virtual methods of T.函数 Build(params object[] args) 应该返回一个从 T 派生的新类的新实例,我们称之为 T',它重新定义了 T 的虚方法。
      • The new class T' is created dynamically with the API resource System.Reflection.Emit.新类 T' 是使用 API 资源 System.Reflection.Emit 动态创建的。
      • For each M method in T that is marked ( Custom Attribute), it should be created a new definition of the method M' in class T'.对于 T 中标记的每个 M 方法(自定义属性),应该在类 T' 中创建方法 M' 的新定义。 The method M' should call the base method M and should do the specified by the custom attribute.方法 M' 应该调用基本方法 M 并且应该执行自定义属性指定的操作。

I'd start by verifying that T is neither sealed nor abstract.我首先要验证T既不是密封的也不是抽象的。 That should be sufficient to make sure it's (1) a class;这应该足以确保它是(1)一个类; and (2) capable of being extended. (2) 可扩展。

Next, iterate over typeof(T).GetProperties() to find any 'enhanced' properties where CanWrite is true and property.GetCustomAttributes<EnhancedAtributte>().Any() reports true .接下来,迭代typeof(T).GetProperties()以查找CanWritetrueproperty.GetCustomAttributes<EnhancedAtributte>().Any()报告true任何“增强型”属性。 If there aren't any matching properties, you can just instantiate T directly.如果没有任何匹配的属性,您可以直接实例化T

Since the property values are validated by the attribute instances themselves, you'll want to cache the attributes somewhere, lest you incur an expensive lookup on each property change.由于属性值由属性实例本身验证,因此您需要将属性缓存在某处,以免在每次属性更改时进行昂贵的查找。 You should aim to generate a class that looks something like this:你的目标应该是生成一个看起来像这样的类:

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

A TypeBuilder can be created from a ModuleBuilder , which is created from an AssemblyBuilder . TypeBuilder可以从ModuleBuilder创建,而ModuleBuilder是从AssemblyBuilder创建的。 For the latter two, you can just keep a static instance around.对于后两者,您可以只保留一个静态实例。

You'll need to use TypeBuilder.DefineField to define a static field to use as an attribute cache for each property (or use a single EnhancedAttribute[][] indexed by property).您需要使用TypeBuilder.DefineField来定义一个静态字段以用作每个属性的属性缓存(或使用按属性索引的单个EnhancedAttribute[][] )。 In either case, you'll have to define a class constructor (see TypeBuilder.DefineTypeInitializer ) to initialize the cache.无论哪种情况,您都必须定义一个类构造函数(请参阅TypeBuilder.DefineTypeInitializer )来初始化缓存。 You'll have to write the IL yourself using MethodBuilder.GetILGenerator() .您必须使用MethodBuilder.GetILGenerator()自己编写 IL。

For each enhanced property you found, you'll need to define a new property with the same name (see TypeBuilder.DefineProperty ), and emit a separate get and set accessor for each (see TypeBuilder.DefineMethod ).对于您找到的每个增强属性,您需要定义一个具有相同名称的新属性(请参阅TypeBuilder.DefineProperty ),并为每个属性发出单独的getset访问器(请参阅TypeBuilder.DefineMethod )。 Each accessor will need to be bound to the property, which can be accomplished via PropertyBuilder.SetGetMethod and SetSetMethod .每个访问器都需要绑定到属性,这可以通过PropertyBuilder.SetGetMethodSetSetMethod完成。 You'll also have to make the accessors override the accessors in T , which you can do via TypeBuilder.DefineMethodOverride .您还必须使访问器覆盖T ,您可以通过TypeBuilder.DefineMethodOverride You can see some hints on overriding properties in this question .您可以在此问题中看到有关覆盖属性的一些提示。

The code for the get accessor override will be simple: you need only delegate to the base property. get访问器覆盖的代码很简单:您只需要委托给基本属性。 The set accessor is more complicated, because you'll need to loop over the attribute cache for the property and call each attribute's Check method. set访问器更复杂,因为您需要遍历属性的属性缓存并调用每个属性的Check方法。 Again, you'll need to emit the IL manually, which includes figuring out how to emit a simple for loop.同样,您需要手动发出 IL,其中包括弄清楚如何发出简单的for循环。 Alternatively, since you already know the number of attributes for each property, you could just write a manually-unrolled loop.或者,由于您已经知道每个属性的属性数量,您可以只编写一个手动展开的循环。 Regardless, for each call to Check , remember that you'll need to initialize a new object[] containing into which you must copy your value parameter.无论如何,对于每次调用Check ,请记住您需要初始化一个新的object[] ,您必须将value参数复制到其中。

Once you've declared the attribute cache field(s), the type initializer, the properties, and their accessors, you're essentially finished.一旦您声明了属性缓存字段、类型初始值设定项、属性及其访问器,您基本上就完成了。 You can 'bake' the derived type by calling CreateType() on your TypeBuilder .您可以通过在TypeBuilder上调用CreateType()来“烘焙”派生类型。


Partial Solution部分解决方案

I felt like writing some code, so here's a solution that should handle the attribute-based property validation.我想写一些代码,所以这里有一个应该处理基于属性的属性验证的解决方案。 It's not entirely clear to me how the attributes on other methods should work, but this should provide a good starting point nonetheless.我并不完全清楚其他方法上的属性应该如何工作,但这应该提供一个很好的起点。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM