我想要一个类,循环它的属性,获取属性值,并调用一个传递该属性值的方法。我想我可以得到属性值,但是lambda表达式的主体是什么样的?用什么体来调用每个属性的方法?
这就是我到目前为止......
Action<T> CreateExpression<T>( T obj )
{
foreach( var property in typeof( T ).GetProperties() )
{
Expression value = Expression.Property( Expression.Constant( obj ), property );
var method = Expression.Call( typeof( SomeType ), "SomeMethod", null, value );
}
// What expression body can be used that will call
// all the method expressions for each property?
var body = Expression...
return Expression.Lambda<Action<T>>( body, ... ).Compile();
}
答案 0 :(得分:4)
这取决于一些事情。
该方法是否会返回任何内容? 3.5中的Expression
不能执行多个单独的“操作”操作(语句体),但如果您可以使用流畅的API执行某些操作,则可以作弊:
SomeMethod(obj.Prop1).SomeMethod(obj.Prop2).SomeMethod(obj.Prop3);
(也许使用泛型使其变得更简单)
你有权访问4.0吗?在4.0中,还有其他Expression
类型允许语句体,完全您要求的内容。我讨论了一些类似的例子in an article here(寻找Expression.Block
,虽然这是基于之前的测试版 - 现在可能已经重命名了。)
替代;既然您要编写代理,请考虑Action<T>
是多播;您可以构建简单操作的集,并将它们组合在委托中;这将适用于3.5;例如:
using System;
using System.Linq.Expressions;
static class SomeType
{
static void SomeMethod<T>(T value)
{
Console.WriteLine(value);
}
}
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
static class Program
{
static readonly Action<Customer> action = CreateAction<Customer>();
static void Main()
{
Customer cust = new Customer { Id = 123, Name = "Abc" };
action(cust);
}
static Action<T> CreateAction<T>()
{
Action<T> result = null;
var param = Expression.Parameter(typeof(T), "obj");
foreach (var property in typeof(T).GetProperties(
BindingFlags.Instance | BindingFlags.Public))
{
if (property.GetIndexParameters().Length > 0) continue;
var propVal = Expression.Property(param, property);
var call = Expression.Call(typeof(SomeType), "SomeMethod", new Type[] {propVal.Type}, propVal);
result += Expression.Lambda<Action<T>>(call, param).Compile();
}
return result;
}
}
答案 1 :(得分:3)
我认为至少在.NET 3.5中使用Expressions会非常容易。
.NET 4支持我相信的块构造。
我建议使用Reflection.Emit。
这是一个起点(对于字段,但可以轻松更改):
internal static T CreateDelegate<T>(this DynamicMethod dm) where T : class
{
return dm.CreateDelegate(typeof(T)) as T;
}
static Dictionary<Type, Func<object, Dictionary<string, object>>> fieldcache =
new Dictionary<Type, Func<object, Dictionary<string, object>>>();
static Dictionary<string, object> GetFields(object o)
{
var t = o.GetType();
Func<object, Dictionary<string, object>> getter;
if (!fieldcache.TryGetValue(t, out getter))
{
var rettype = typeof(Dictionary<string, object>);
var dm = new DynamicMethod(t.Name + ":GetFields",
rettype, new Type[] { typeof(object) }, t);
var ilgen = dm.GetILGenerator();
var instance = ilgen.DeclareLocal(t);
var dict = ilgen.DeclareLocal(rettype);
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Castclass, t);
ilgen.Emit(OpCodes.Stloc, instance);
ilgen.Emit(OpCodes.Newobj, rettype.GetConstructor(Type.EmptyTypes));
ilgen.Emit(OpCodes.Stloc, dict);
var add = rettype.GetMethod("Add");
foreach (var field in t.GetFields(
BindingFlags.DeclaredOnly |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic))
{
if (!field.FieldType.IsSubclassOf(typeof(Component)))
{
continue;
}
ilgen.Emit(OpCodes.Ldloc, dict);
ilgen.Emit(OpCodes.Ldstr, field.Name);
ilgen.Emit(OpCodes.Ldloc, instance);
ilgen.Emit(OpCodes.Ldfld, field);
ilgen.Emit(OpCodes.Castclass, typeof(object));
ilgen.Emit(OpCodes.Callvirt, add);
}
ilgen.Emit(OpCodes.Ldloc, dict);
ilgen.Emit(OpCodes.Ret);
fieldcache[t] = getter = dm.CreateDelegate<Func<object,
Dictionary<string, object>>>();
}
return getter(o);
}
答案 2 :(得分:2)
使用Block语句。例如,下面的代码会写出所有属性的名称
static void WritePropertyNames()
{
TestObject lTestObject = new TestObject();
PropertyInfo[] lProperty = typeof(TestObject).GetProperties();
List<Expression> lExpressions = new List<Expression>();
MethodInfo lMethodInfo = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
lProperty.ForEach(x =>
{
ConstantExpression lConstant = Expression.Constant(x.Name);
MethodCallExpression lMethodCall = Expression.Call(lMethodInfo, lConstant);
lExpressions.Add(lMethodCall);
});
BlockExpression lBlock = Expression.Block(lExpressions);
LambdaExpression lLambda = Expression.Lambda<Action>(lBlock, null);
Action lWriteProperties = lLambda.Compile() as Action;
lWriteProperties();
}
答案 3 :(得分:0)
表达式树只能包含一个语句。要做你正在尝试的事情,你需要在循环中Expression.Lambda<>()
,将“方法”作为正文传递。
我相信这在.NET Framework 4.0中已经发生了变化。
安德鲁
答案 4 :(得分:0)
如果您愿意让方法SomeType.SomeMethod
接受object[]
,那么您可以执行以下操作(请注意,此处无法处理索引器,因此我们将其丢弃):
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Test {
class SomeType {
public static void SomeMethod(object[] values) {
foreach (var value in values) {
Console.WriteLine(value);
}
}
}
class Program {
static Action<T> CreateAction<T>() {
ParameterExpression parameter = Expression.Parameter(
typeof(T),
"parameter"
);
List<Expression> properties = new List<Expression>();
foreach (var info in typeof(T).GetProperties()) {
// can not handle indexers
if(info.GetIndexParameters().Length == 0) {
Expression property = Expression.Property(parameter, info);
properties.Add(Expression.Convert(property, typeof(object)));
}
}
Expression call = Expression.Call(
typeof(SomeType).GetMethod("SomeMethod"),
Expression.NewArrayInit(typeof(object), properties)
);
return Expression.Lambda<Action<T>>(call, parameter).Compile();
}
static void Main(string[] args) {
Customer c = new Customer();
c.Name = "Alice";
c.ID = 1;
CreateAction<Customer>()(c);
}
}
class Customer {
public string Name { get; set; }
public int ID { get; set; }
}
}
当然,使用LoopExpression
的.NET 4.0会更容易。