表达式调用类的每个属性的方法

时间:2010-01-15 19:40:26

标签: c# lambda expression-trees

我想要一个类,循环它的属性,获取属性值,并调用一个传递该属性值的方法。我想我可以得到属性值,但是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();
}

5 个答案:

答案 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会更容易。