簡體   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