![](/img/trans.png)
[英]Can I add a dynamic method to an existing type using System.Reflection.Emit?
[英]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) { ... }
}
我首先要验证T
既不是密封的也不是抽象的。 这应该足以确保它是(1)一个类; (2) 可扩展。
接下来,迭代typeof(T).GetProperties()
以查找CanWrite
为true
且property.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
),并为每个属性发出单独的get
和set
访问器(请参阅TypeBuilder.DefineMethod
)。 每个访问器都需要绑定到属性,这可以通过PropertyBuilder.SetGetMethod
和SetSetMethod
完成。 您还必须使访问器覆盖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.