views:

329

answers:

4

I want to take a class, loop through it's properties, get the property value, and call a method passing that property value in. I think I can get the property values, but what does the lambda expression's body look like? What body is used to call a method on each property?

This is what I have so far...

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();
}
+1  A: 

It depends on a few things.

  • does the method return anything? Expression in 3.5 can't do multiple separate "action" operations (a statement body), but you can cheat if you can do something with a fluent API:

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

    (perhaps using generics to make it simpler)

  • do you have access to 4.0? In 4.0 there are additional Expression types allowing statement bodies and exactly what you ask for. I discuss some similar examples in an article here (look for Expression.Block, although this is based on a beta a while ago - it may have been renamed by now).

Alternative; since you are compiling to a delegate, consider that an Action<T> is multicast; you could build a set of simple operations, and combine them in the delegate; this would work in 3.5; for example:

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;
    }
}
Marc Gravell
You can simulate it using AND too :)
leppie
What, by having it return `true` each time?? I guess so. Or you could use null-coalescing. I'd rather use 4.0 though ;-p
Marc Gravell
Trying out your multicast delegate option...
Josh Close
@Marc Gravell: I was just playing around with this some more and I noticed that both your method and my method (http://stackoverflow.com/questions/2074283/expression-to-call-a-method-on-each-property-of-a-class/2074508#2074508) throw a `System.Security.VerificationException` when invoked as `var list = new List<int>(); CreateAction<List<int>>()(list);` The message is "Operation could destabilize the runtime." Any ideas?
Jason
Ah, it looks like it's puking on the indexer on `List<int>`.
Jason
The multicast delete version worked and fits the current model I have (reflection).
Josh Close
wow? I'll investigate...
Marc Gravell
@Marc Gravell: It looks like you didn't see my second comment. It pukes on indexers. Indexer can easily be detected using `PropertyInfo.GetIndexParameters().Length == 0`.
Jason
Indeed I missed that comment and investigated separately. At least we reached the same conclusion ;-p Removing static properties is important too.
Marc Gravell
The time of my test case went from 30 seconds down to 3 seconds when using this expression instead of reflection. Thanks again Marc!
Josh Close
A: 

I dont think it will be so easy using Expressions, in .NET 3.5 at least.

.NET 4 supports a block construct I believe.

I suggest using Reflection.Emit rather.

Here is a starting point (for fields but can be changed easily):

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);
}
leppie
A: 

Expression trees can only contain a single statement. To do what you are trying you would need to Expression.Lambda<>() in your loop, passing "method" as the body.

I believe this has changed in .NET Framework 4.0.

Andrew

Andrew
A: 

If you're willing to have your method SomeType.SomeMethod accept an object[] then you can do something like this (note that indexers can not be handled here so we discard them):

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

Of course this will be easier in .NET 4.0 with the LoopExpression.

Jason