【问题标题】:Expression to Call a Method on Each Property of a Class对类的每个属性调用方法的表达式
【发布时间】:2010-01-15 19:40:25
【问题描述】:

我想获取一个类,遍历它的属性,获取属性值,然后调用一个将属性值传入的方法。我想我可以获取属性值,但是 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();
}

【问题讨论】:

    标签: c# lambda expression-trees


    【解决方案1】:

    这取决于一些事情。

    • 该方法是否返回任何内容? 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&lt;T&gt; 是多播的;您可以构建一个 setsimple 操作,并将它们组合在委托中;这将在 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;
        }
    }
    

    【讨论】:

    • 你也可以使用 AND 来模拟它:)
    • 什么,每次都返回true?大概吧。或者你可以使用空合并。不过我宁愿使用 4.0 ;-p
    • @Marc Gravell:我只是在玩这个,我注意到你的方法和我的方法 (stackoverflow.com/questions/2074283/…) 在调用为 System.Security.VerificationException 时都会抛出 System.Security.VerificationException@ 消息是“操作可能会破坏运行时的稳定性。”有什么想法吗?
    • 啊,它好像在List&lt;int&gt; 的索引器上呕吐。
    • 多播删除版本有效并且适合我当前的模型(反思)。
    【解决方案2】:

    我认为至少在 .NET 3.5 中使用表达式不会那么容易。

    .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);
    }
    

    【讨论】:

      【解决方案3】:

      使用 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();
          }
      

      【讨论】:

        【解决方案4】:

        表达式树只能包含一条语句。要执行您正在尝试的操作,您需要在循环中 Expression.Lambda&lt;&gt;(),将“方法”作为正文传递。

        我相信这在 .NET Framework 4.0 中有所改变。

        安德鲁

        【讨论】:

          【解决方案5】:

          如果您愿意让您的方法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; }
              }
          }
          

          当然,在 .NET 4.0 中使用LoopExpression 会更容易。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-03-30
            相关资源
            最近更新 更多