我试图在运行时生成实现属性获取器的类,其主体调用生成类的基类上的方法。这是一个简单接口的示例,以及我试图复制的手写实现和基类。
public interface IGenerated : IBase { decimal Property1 { get; } }
public class GeneratedByHand : ImplBase<IGenerated> {
public decimal Property1 { get { return Get(s => s.Property1); } }
}
public interface IBase { string _KeyPrefix { get; set; } }
public abstract class ImplBase<T> : IBase
where T : IBase
{
public virtual string _KeyPrefix { get; set; }
protected virtual TResult Get<TResult>(Expression<Func<T, TResult>> property) {
return GetValue<TResult>(GetPropertyName(property));
}
private string GetPropertyName<TResult>(Expression<Func<T, TResult>> property) {
return ""; // reflection stuff to get name from property expression goes here
}
private TResult GetValue<TResult>(string keyPart) {
return default(TResult); // does something like: return ReallyGetValue<TResult>(_KeyPrefix + keyPart);
}
}
我有一个生成器的工作实现,它发出 IL 来构建方法,但如果我可以使用表达式来实现,我认为这将更容易扩展和维护。我将需要在属性定义上查找自定义属性,并使用它来调用属性实现中基类的不同方法重载。
这是我为属性 get 实现构建表达式的地方。我真正不明白的是构建 Call 表达式,如果我正确设置它来做相当于this.Get()
or base.Get()
。现在它抛出System.ArgumentException : Invalid argument value Parameter name: method
一个CompileToMethod
public void CreateExpressionForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
{
var settingsParam = Expression.Parameter(interfaceType, "s");
var propGetterExpr = Expression.Property(settingsParam, property);
var propGetterExprFuncType = typeof(Func<,>).MakeGenericType(interfaceType, property.PropertyType);
var propGetterLambda = Expression.Lambda(propGetterExprFuncType, propGetterExpr, settingsParam);
var baseGetMethodInfo =
baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(m => {
var parameters = m.GetParameters();
return m.Name == "Get" &&
parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
})
.First().MakeGenericMethod(property.PropertyType);
var getExprType = typeof(Expression<>).MakeGenericType(propGetterExprFuncType);
var getExprParam = Expression.Parameter(getExprType, "expression");
var getCallExpr = Expression.Call(Expression.Parameter(baseType, "inst"), baseGetMethodInfo, propGetterLambda);
var getFuncType = typeof(Func<,>).MakeGenericType(getExprType, property.PropertyType);
var propLambda = Expression.Lambda(getFuncType, getCallExpr, getExprParam);
propLambda.CompileToMethod(getBuilder);
}
我真的不知道从这里去哪里。我已经尝试了 Expression.Call 的其他一些参数变体,但其他所有参数都有 Call 抛出异常,因为参数是错误的类型。
这是我正在使用的所有示例代码的可构建版本,包括工作的 IL 发射器:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using NUnit.Framework;
namespace ExpressionGenerationTest
{
[TestFixture]
public class UnitTests
{
[Test]
public void CreateAndSaveAssembly()
{
var implGenerator = new ImplBuilder();
var generatedType = implGenerator.CreateImplementation(typeof(IGenerated));
implGenerator.SaveAssembly();
}
}
public interface IBase { string _KeyPrefix { get; set; } }
public abstract class ImplBase<T> : IBase
where T : IBase
{
public virtual string _KeyPrefix { get; set; }
protected virtual TResult Get<TResult>(Expression<Func<T, TResult>> property) { return GetValue<TResult>(GetPropertyName(property)); }
private string GetPropertyName<TResult>(Expression<Func<T, TResult>> property) { return ""; } // reflection stuff to get name from property expression goes here
private TResult GetValue<TResult>(string keyPart) { return default(TResult); } // does something like: return ReallyGetValue(_KeyPrefix + keyPart);
}
public interface IGenerated : IBase { decimal Property1 { get; } }
public class GeneratedByHand : ImplBase<IGenerated> { public decimal Property1 { get { return Get(s => s.Property1); } } }
public class ImplBuilder
{
private const string _assemblyNameBase = "ExpressionGenerationTest.Impl";
public static ImplBuilder Default { get { return _default.Value; } }
private static readonly Lazy<ImplBuilder> _default = new Lazy<ImplBuilder>(() => new ImplBuilder());
private ConcurrentDictionary<Type, Type> _types = new ConcurrentDictionary<Type, Type>();
private AssemblyBuilder _assemblyBuilder = null;
private volatile ModuleBuilder _moduleBuilder = null;
private object _lock = new object();
private void EnsureInitialized()
{
if (_moduleBuilder == null) {
lock (_lock) {
if (_moduleBuilder == null) {
_assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(_assemblyNameBase), AssemblyBuilderAccess.RunAndSave);
_moduleBuilder = _assemblyBuilder.DefineDynamicModule(_assemblyBuilder.GetName().Name, _assemblyNameBase + ".dll");
}
}
}
}
public void SaveAssembly() { _assemblyBuilder.Save(_assemblyNameBase + ".dll"); }
public TSettings CreateInstance<TSettings>() { return (TSettings)Activator.CreateInstance(_types.GetOrAdd(typeof(TSettings), CreateImplementation)); }
public void CreateImplementations(IEnumerable<Type> types) { foreach (var t in types) _types.GetOrAdd(t, InternalCreateImplementation); }
public Type CreateImplementation(Type interfaceType) { return _types.GetOrAdd(interfaceType, InternalCreateImplementation); }
private Type InternalCreateImplementation(Type interfaceType)
{
EnsureInitialized();
var baseType = typeof (ImplBase<>).MakeGenericType(interfaceType);
var typeBuilder = _moduleBuilder.DefineType(
(interfaceType.IsInterface && interfaceType.Name.StartsWith("I")
? interfaceType.Name.Substring(1)
: interfaceType.Name) + "Impl",
TypeAttributes.Public | TypeAttributes.Class |
TypeAttributes.AutoClass | TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
baseType,
new [] {interfaceType});
foreach (var p in GetPublicProperties(interfaceType).Where(pi => pi.DeclaringType != typeof(IBase))) {
var iGet = p.GetGetMethod();
if (iGet != null) {
var getBuilder =
typeBuilder.DefineMethod(iGet.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
p.PropertyType, Type.EmptyTypes);
//EmitILForGetMethod(getBuilder, interfaceType, baseType, p, iGet);
CreateExpressionForGetMethod(getBuilder, interfaceType, baseType, p, iGet);
typeBuilder.DefineMethodOverride(getBuilder, iGet);
}
}
var implementationType = typeBuilder.CreateType();
return implementationType;
}
public void CreateExpressionForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
{
var settingsParam = Expression.Parameter(interfaceType, "s");
var propGetterExpr = Expression.Property(settingsParam, property);
var propGetterExprFuncType = typeof(Func<,>).MakeGenericType(interfaceType, property.PropertyType);
var propGetterLambda = Expression.Lambda(propGetterExprFuncType, propGetterExpr, settingsParam);
var baseGetMethodInfo =
baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(m => {
var parameters = m.GetParameters();
return m.Name == "Get" &&
parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
})
.First().MakeGenericMethod(property.PropertyType);
var getExprType = typeof(Expression<>).MakeGenericType(propGetterExprFuncType);
var getExprParam = Expression.Parameter(getExprType, "expression");
var getCallExpr = Expression.Call(Expression.Parameter(baseType, "inst"), baseGetMethodInfo, propGetterLambda);
var getFuncType = typeof(Func<,>).MakeGenericType(getExprType, property.PropertyType);
var propLambda = Expression.Lambda(getFuncType, getCallExpr, getExprParam);
propLambda.CompileToMethod(getBuilder);
}
public void EmitILForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
{
var getGen = getBuilder.GetILGenerator();
var retVal = getGen.DeclareLocal(property.PropertyType);
var expParam = getGen.DeclareLocal(typeof(ParameterExpression));
var expParams = getGen.DeclareLocal(typeof(ParameterExpression[]));
getGen.Emit(OpCodes.Ldarg_0);
getGen.Emit(OpCodes.Ldtoken, interfaceType);
getGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
getGen.Emit(OpCodes.Ldstr, "s");
getGen.Emit(OpCodes.Call, typeof(Expression).GetMethod("Parameter", new [] {typeof(Type), typeof(string)}));
getGen.Emit(OpCodes.Stloc, expParam);
getGen.Emit(OpCodes.Ldloc, expParam);
getGen.Emit(OpCodes.Ldtoken, getMethod);
getGen.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new [] {typeof(RuntimeMethodHandle)}, null));
getGen.Emit(OpCodes.Castclass, typeof(MethodInfo));
getGen.Emit(OpCodes.Call, typeof(Expression).GetMethod("Property", new[] {typeof(Expression), typeof(MethodInfo)}));
getGen.Emit(OpCodes.Ldc_I4_1);
getGen.Emit(OpCodes.Newarr, typeof(ParameterExpression));
getGen.Emit(OpCodes.Stloc, expParams);
getGen.Emit(OpCodes.Ldloc, expParams);
getGen.Emit(OpCodes.Ldc_I4_0);
getGen.Emit(OpCodes.Ldloc, expParam);
getGen.Emit(OpCodes.Stelem_Ref);
getGen.Emit(OpCodes.Ldloc, expParams);
var lambdaMethodInfo =
typeof(Expression).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => {
var parameters = x.GetParameters();
return x.Name == "Lambda" &&
x.IsGenericMethodDefinition &&
parameters.Count() == 2 &&
parameters[0].ParameterType == typeof(Expression) &&
parameters[1].ParameterType == typeof(ParameterExpression[]);
}).FirstOrDefault();
var lambdaFuncType = typeof(Func<,>);
lambdaFuncType = lambdaFuncType.MakeGenericType(interfaceType, property.PropertyType);
lambdaMethodInfo = lambdaMethodInfo.MakeGenericMethod(lambdaFuncType);
getGen.Emit(OpCodes.Call, lambdaMethodInfo);
var baseGetMethodInfo =
baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(m => {
var parameters = m.GetParameters();
return m.Name == "Get" &&
parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
}).FirstOrDefault();
baseGetMethodInfo = baseGetMethodInfo.MakeGenericMethod(property.PropertyType);
getGen.Emit(OpCodes.Callvirt, baseGetMethodInfo);
getGen.Emit(OpCodes.Stloc_0);
var endOfMethod = getGen.DefineLabel();
getGen.Emit(OpCodes.Br_S, endOfMethod);
getGen.MarkLabel(endOfMethod);
getGen.Emit(OpCodes.Ldloc_0);
getGen.Emit(OpCodes.Ret);
}
// from http://stackoverflow.com/a/2444090/224087
public static PropertyInfo[] GetPublicProperties(Type type)
{
if (!type.IsInterface)
return type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
var propertyInfos = new List<PropertyInfo>();
var considered = new List<Type>();
var queue = new Queue<Type>();
considered.Add(type);
queue.Enqueue(type);
while (queue.Count > 0) {
var subType = queue.Dequeue();
foreach (var subInterface in subType.GetInterfaces()) {
if (considered.Contains(subInterface))
continue;
considered.Add(subInterface);
queue.Enqueue(subInterface);
}
var typeProperties = subType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
var newPropertyInfos = typeProperties.Where(x => !propertyInfos.Contains(x));
propertyInfos.InsertRange(0, newPropertyInfos);
}
return propertyInfos.ToArray();
}
}
}