简体   繁体   中英

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.


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. 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.

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.
      • 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.
      • The new class T' is created dynamically with the API resource 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'. The method M' should call the base method M and should do the specified by the custom attribute.

I'd start by verifying that T is neither sealed nor abstract. That should be sufficient to make sure it's (1) a class; and (2) capable of being extended.

Next, iterate over typeof(T).GetProperties() to find any 'enhanced' properties where CanWrite is true and property.GetCustomAttributes<EnhancedAtributte>().Any() reports true . If there aren't any matching properties, you can just instantiate T directly.

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 . 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). In either case, you'll have to define a class constructor (see TypeBuilder.DefineTypeInitializer ) to initialize the cache. You'll have to write the IL yourself using MethodBuilder.GetILGenerator() .

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 ). Each accessor will need to be bound to the property, which can be accomplished via PropertyBuilder.SetGetMethod and SetSetMethod . You'll also have to make the accessors override the accessors in T , which you can do via 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. 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. Again, you'll need to emit the IL manually, which includes figuring out how to emit a simple for loop. 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.

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 .


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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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