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