我试图在运行时生成类,这些类实现属性 getter ,其主体调用生成类的基类上的方法。这是一个简单接口(interface)的示例,以及我试图复制的手写实现和基类。
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()
或 base.Get()
等效的操作>。现在,它在 CompileToMethod
System.ArgumentException : Invalid argument value Parameter name: method
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();
}
}
}
最佳答案
What I don't really understand is building the Call expression, if I'm setting it up correctly to do the equivalent of this.Get() or base.Get().
如果您正在调用虚拟方法,则 this.Get()
,它访问最派生的覆盖(甚至可以在当前类的后代中定义),使用 callvirt
说明。无论您反射(reflect)什么类型以获得 MethodInfo
都没有关系。 ,因为它们都共享相同的虚拟表槽。
发出base.Get()
,你必须
- 使用
call
说明 - 反射(reflect)基类类型
因为callvirt
除了 v 表查找之外,还执行一些额外的操作,包括空指针检查,C# 编译器将其用于所有虚拟和非虚拟调用,除了那些涉及 base
的调用。关键字。
特别是,匿名委托(delegate)和 lambda 无法使用 base
关键字,因为只有后代类型可以对虚拟方法进行非虚拟调用(至少在可验证的代码中),并且 lambda 实际上由闭包类型托管。
不幸的是,对于您的用例,无法使用 lambda 表示法或表达式树来表达基调用。 Expression.CompileToMethod
只生成callvirt
。嗯,这并不完全正确。它生成call
用于调用值类型的静态方法和实例方法。但引用类型的实例方法仅使用 callvirt
。您可以在 System.Linq.Expressions.Compiler.LambdaCompiler.UseVirtual
中看到这一点
感谢 @hvd 根据 the Microsoft Reference Source for UseVirtual
中的评论确认了这一点
关于c# - 如何使用表达式生成此属性实现而不是发出 IL?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23019297/