繁体   English   中英

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

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

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

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


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


添加的功能由标记虚拟方法和属性的自定义属性指定,如以下示例所示。 在评论中说我们打算在从 Enhancer 的函数 Build 返回的对象中开始验证标记的方法和属性。 示例中的每个自定义属性都应该有一个公共基类型 - 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) { ... }
 }
  • 实施策略:
    • 项目增强器必须使用 System.Reflection.Emit API。
      • 解决方案不得与给定的示例妥协。
      • 函数 Build(params object[] args) 应该返回一个从 T 派生的新类的新实例,我们称之为 T',它重新定义了 T 的虚方法。
      • 新类 T' 是使用 API 资源 System.Reflection.Emit 动态创建的。
      • 对于 T 中标记的每个 M 方法(自定义属性),应该在类 T' 中创建方法 M' 的新定义。 方法 M' 应该调用基本方法 M 并且应该执行自定义属性指定的操作。

我首先要验证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可以从ModuleBuilder创建,而ModuleBuilder是从AssemblyBuilder创建的。 对于后两者,您可以只保留一个静态实例。

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

对于您找到的每个增强属性,您需要定义一个具有相同名称的新属性(请参阅TypeBuilder.DefineProperty ),并为每个属性发出单独的getset访问器(请参阅TypeBuilder.DefineMethod )。 每个访问器都需要绑定到属性,这可以通过PropertyBuilder.SetGetMethodSetSetMethod完成。 您还必须使访问器覆盖T ,您可以通过TypeBuilder.DefineMethodOverride 您可以在此问题中看到有关覆盖属性的一些提示。

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

一旦您声明了属性缓存字段、类型初始值设定项、属性及其访问器,您基本上就完成了。 您可以通过在TypeBuilder上调用CreateType()来“烘焙”派生类型。


部分解决方案

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

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