Выражение для вызова метода для каждого свойства класса - PullRequest
2 голосов
/ 15 января 2010

Я хочу взять класс, просмотреть его свойства, получить значение свойства и вызвать метод, передающий это значение свойства. Я думаю, что могу получить значения свойств, но как выглядит тело лямбда-выражения? Какое тело используется для вызова метода для каждого свойства?

Это то, что я имею до сих пор ...

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 ]

4 голосов
/ 15 января 2010

Это зависит от нескольких вещей.

  • метод возвращает что-нибудь? Expression в 3.5 не может выполнять несколько отдельных операций "action" (тело оператора), но вы можете cheat , если вы можете что-то сделать с помощью свободного API:

    SomeMethod(obj.Prop1).SomeMethod(obj.Prop2).SomeMethod(obj.Prop3);
    

    (возможно, используя дженерики для упрощения)

  • у вас есть доступ к 4.0? В 4.0 есть дополнительные Expression типы, допускающие тела выписок, и в точности , которые вы запрашиваете. Я обсуждаю некоторые подобные примеры в статье здесь (ищите Expression.Block, хотя она основана на бета-версии некоторое время назад - возможно, она уже была переименована)

Alternative; поскольку вы компилируете в делегат, учтите, что Action<T> является многоадресной рассылкой; вы можете создать set из простых операций и объединить их в делегате; это будет работать в 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;
    }
}
3 голосов
/ 15 января 2010

Не думаю, что с помощью Expressions будет так легко, по крайней мере, в .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);
}
2 голосов
/ 14 сентября 2012

Используйте оператор блокировки.Например, код ниже записывает имена всех свойств

    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();
    }
0 голосов
/ 15 января 2010

Если вы хотите, чтобы ваш метод 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.

0 голосов
/ 15 января 2010

Деревья выражений могут содержать только один оператор.Чтобы сделать то, что вы пытаетесь, вам нужно Expression.Lambda<>() в вашем цикле, передавая "method" в качестве тела.

Я считаю, что это изменилось в .NET Framework 4.0.

Andrew

...