Reading Properties of an Object with Expression Trees - c#

I want to create a Lambda Expression for every Property of an Object that reads the value dynamically.
What I have so far:
var properties = typeof (TType).GetProperties().Where(p => p.CanRead);
foreach (var propertyInfo in properties)
{
var getterMethodInfo = propertyInfo.GetGetMethod();
var entity = Expression.Parameter(typeof (TType));
var getterCall = Expression.Call(entity, getterMethodInfo);
var lambda = Expression.Lambda(getterCall, entity);
var expression = (Expression<Func<TType, "TypeOfProperty">>) lambda;
var functionThatGetsValue = expression.Compile();
}
The code Works well when i call functionThatGetsValue as long as "TypeOfProperty" is hardcoded. I know that I can't pass the "TypeOfPoperty" dynamically. What can I do to achive my goal?

Assuming that you're happy with a Func<TType, object> delegate (as per the comments above), you can use Expression.Convert to achieve that:
var properties = typeof(TType).GetProperties().Where(p => p.CanRead);
foreach (var propertyInfo in properties)
{
MethodInfo getterMethodInfo = propertyInfo.GetGetMethod();
ParameterExpression entity = Expression.Parameter(typeof(TType));
MethodCallExpression getterCall = Expression.Call(entity, getterMethodInfo);
UnaryExpression castToObject = Expression.Convert(getterCall, typeof(object));
LambdaExpression lambda = Expression.Lambda(castToObject, entity);
var functionThatGetsValue = (Func<TType, object>)lambda.Compile();
}

After hours of googling found the answer here. I've added the snippets from the blog post as it might help others having the same troubles:
public static class PropertyInfoExtensions
{
public static Func<T, object> GetValueGetter<T>(this PropertyInfo propertyInfo)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<T, object>)Expression.Lambda(convert, instance).Compile();
}
public static Action<T, object> GetValueSetter<T>(this PropertyInfo propertyInfo)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var argument = Expression.Parameter(typeof(object), "a");
var setterCall = Expression.Call(
instance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return (Action<T, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
}

I've modified gsharp's post above to actually set the value directly and make it a bit easier to use. It's not ideal as there is the introduction of the DynamicCast function which requires you to know your type up front. My goal was to try to keep us strongly typed and not return object and avoid dynamic keyword. Also, keep "magic" to a minimum.
public static T DynamicCast<T>(this object value)
{
return (T) value;
}
public static object GetPropertyValue<T>(this PropertyInfo propertyInfo, T objectInstance)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, propertyInfo.PropertyType);
var lambda = Expression.Lambda(convert, instance).Compile();
var result = lambda.DynamicInvoke(objectInstance);
return result;
}
public static void SetPropertyValue<T, TP>(this PropertyInfo propertyInfo, T objectInstance, TP value)
where T : class
where TP : class
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var argument = Expression.Parameter(propertyInfo.PropertyType, "a");
var setterCall = Expression.Call(
instance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
var lambda = Expression.Lambda(setterCall, instance, argument).Compile();
lambda.DynamicInvoke(objectInstance, value);
}
Examples:
public void Get_Value_Of_Property()
{
var testObject = new ReflectedType
{
AReferenceType_No_Attributes = new object(),
Int32WithRange1_10 = 5,
String_Requires = "Test String"
};
var result = testObject.GetType().GetProperty("String_Requires").GetPropertyValue(testObject).DynamicCast<string>();
result.Should().Be(testObject.String_Requires);
}
public void Set_Value_Of_Property()
{
var testObject = new ReflectedType
{
AReferenceType_No_Attributes = new object(),
Int32WithRange1_10 = 5,
String_Requires = "Test String"
};
testObject.GetType().GetProperty("String_Requires").SetPropertyValue(testObject, "MAGIC");
testObject.String_Requires.Should().Be("MAGIC");
}
You could write a helper method which uses MakeGenericMethod or an expression tree to make a lambda do the typed call to call DynamicCast based on the PropertyInfo object and avoid having to know it up front. But that is less elegant.

Related

How to generically create an Expression for a property?

ExpressMapper has an Ignore method that looks like this:
public IMemberConfiguration<T, TN> Ignore<TMember>(Expression<Func<TN, TMember>> dest)
{
if (dest == null)
{
throw new ArgumentNullException("dst");
}
if (!(dest.Body is MemberExpression))
{
throw new Exception("MemberExpression should return one of the properties of destination class");
}
foreach (var typeMapper in _typeMappers)
{
typeMapper.Ignore(dest);
}
return this;
}
I would like to add my own IgnoreAll method that iterates over all properties on a Type and calls the Ignore method for each property. This is what I have so far:
public static IMemberConfiguration<TSource, TDestination> IgnoreAll<TSource, TDestination>(
this IMemberConfiguration<TSource, TDestination> config)
{
var props = typeof (TDestination).GetProperties();
foreach (var prop in props)
{
var propertyInfo = typeof(TDestination).GetProperty(prop.Name);
var entityParam = Expression.Parameter(typeof(TDestination), "e");
Expression columnExpr = Expression.Property(entityParam, prop);
if (propertyInfo.PropertyType != typeof(object))
columnExpr = Expression.Convert(columnExpr, typeof(object));
var expression = Expression.Lambda<Func<TDestination, object>>(columnExpr, entityParam);
config.Ignore(expression);
}
return config;
}
When this code is run, I get an error:
MemberExpression should return one of the properties of destination
class
As you can see from the source of the Ignore method above, my generated expression is failing the following conditional:
if (!(dest.Body is MemberExpression))
{
throw new Exception("MemberExpression should return one of the properties of destination class");
}
So my question is:
What do I need to change in my extension method to make the correct Expression that the Ignore method is expecting?
Edit: by the way, the full source for the MemberConfiguration class is here: https://github.com/fluentsprings/ExpressMapper/blob/master/ExpressMapper%20NET40/MemberConfiguration.cs
Okay I figured this out, and I gotta tell ya, it was a doozy.
public static IMemberConfiguration<TSource, TDestination> IgnoreAll<TSource, TDestination>(
this IMemberConfiguration<TSource, TDestination> config)
{
// First we'll get the collection of properties to iterate over.
var props = typeof (TDestination).GetProperties();
foreach (var prop in props)
{
// Get the property information.
var propertyInfo = typeof(TDestination).GetProperty(prop.Name);
// Create an expression that points to the property.
var entityParameter = new ParameterExpression[]
{
Expression.Parameter(typeof(TDestination), "e")
};
var propertyExpression = Expression.Property(entityParameter[0], prop);
// Create a Func<,> using the TDestination and the property's type
// for the Type parameters.
var funcType = typeof(Func<,>).MakeGenericType(typeof(TDestination), propertyInfo.PropertyType);
// We need to create an Expression using Expression.Lambda<>, but we
// don't know the types involved so we have to do this using reflection.
var lambdaMethod = typeof (Expression)
.GetMethods()
.Single(m => m.IsGenericMethod &&
m.GetParameters()[0].ParameterType == typeof(Expression) &&
m.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));
var lambdaMethodConstructed = lambdaMethod.MakeGenericMethod(funcType);
var expression = lambdaMethodConstructed.Invoke(
null,
new object[] { propertyExpression, entityParameter });
// Now we need to construct the Ignore method using the property's Type.
var ignoreMethod = config.GetType().GetMethod("Ignore");
var constructed = ignoreMethod.MakeGenericMethod(propertyInfo.PropertyType);
// Finally, we call the constructed Ignore method, using
// our expression as the argument.
constructed.Invoke(config, new object[] { expression });
}
return config;
}

Conditional method calls in an expression tree

I'm trying to add an additional method call to my expression tree, but I'm slightly confused how to implement it. Here is what I'm currently working with:
private static Action<object, object> CreateSetter(SetterInfo info)
{
var propertyInfo = info.Type.GetProperty(info.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
return (s, v) => { };
var objParameter = Expression.Parameter(typeof(object));
var valueParameter = Expression.Parameter(typeof(object));
//This is the method call I'm trying to add
if (info.Name[0] == 'G' && info.Type.Name == TaxDataConstant.ParcelFeat)
{
var convertParcelFeatCall = Expression.Call(ConvertParcelFeatMethod, valueParameter, Expression.Constant(info.Name));
}
var changeTypeCall = Expression.Call(ChangeTypeMethod, valueParameter, Expression.Constant(propertyInfo.PropertyType));
var objCast = Expression.Convert(objParameter, info.Type);
var valueCast = Expression.Convert(changeTypeCall, propertyInfo.PropertyType);
var property = Expression.Property(objCast, propertyInfo);
var assignment = Expression.Assign(property, valueCast);
var lambda = Expression.Lambda<Action<object, object>>(assignment, objParameter, valueParameter);
return lambda.Compile();
}
What I want to happen is:
1) If the name of the type in my SetterInfo object is ParcelFeat and the Properties name begins with 'G' I want to call ConvertParcelFeat on valueParameter and then call ChangeType on the return.
2) If the name of the type is anything other than ParcelFeat call Changetype as normal with out the additional steps
What I'm confused is how to build the conditional. I'm assuming the way I'm doing it in the above code is wrong and I need to use something like Expression.IfThen() to to build the conditional. I'm also unsure how I can chain the method calls like I want.
You do not need in Expression.IfThen because for each specific SetterInfo you combine exactly one specific lambda instance.
Just plug in convertParcelFeatCall in proper place of your ExpressionTree and all should work just fine.
So your code might look like:
class Program
{
static void Main(string[] args)
{
var program = new Program();
var weightLambda = program.DoInternal("Weight").ToString()
== "(Param_0, Param_1) => (Convert(Param_0).Weight = Convert(ChangeType(Param_1, System.Object)))";
var goodiesLambda = program.DoInternal("Goodies").ToString()
== "(Param_0, Param_1) => (Convert(Param_0).Goodies = Convert(ChangeType(Param_1, ConvertParcelFeat(Param_1, \"Goodies\"))))";
Console.WriteLine("WeightLambda is Ok: {0}\nGoodiesLambda is Ok: {1}", weightLambda, goodiesLambda);
}
public Action<Object, Object> Do(string name)
{
return DoInternal(name).Compile();
}
public Expression<Action<object, object>> DoInternal(string name)
{
var info = new {Name = name, Type = typeof(Program)};
var propertyInfo = info.Type.GetProperty(info.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var objParameter = Expression.Parameter(typeof(object));
var valueParameter = Expression.Parameter(typeof(object));
//This is the method call I'm trying to add
Expression toBeTypeChanged;
if (info.Name[0] == 'G' && info.Type.Name == "Program")
{
toBeTypeChanged = Expression.Call(ConvertParcelFeatMethod, valueParameter, Expression.Constant(info.Name));
}
else
{
toBeTypeChanged = Expression.Constant(propertyInfo.PropertyType);
}
var changeTypeCall = Expression.Call(ChangeTypeMethod, valueParameter, toBeTypeChanged);
var objCast = Expression.Convert(objParameter, info.Type);
var valueCast = Expression.Convert(changeTypeCall, propertyInfo.PropertyType);
var property = Expression.Property(objCast, propertyInfo);
var assignment = Expression.Assign(property, valueCast);
return Expression.Lambda<Action<object, object>>(assignment, objParameter, valueParameter);
}
public object Weight { get; set; }
public object Goodies { get; set; }
public static object ChangeType(object valueParameter, object constant)
{
return null;
}
public static object ConvertParcelFeat(object valueParameter, object constant)
{
return null;
}
public MethodInfo ConvertParcelFeatMethod
{
get { return typeof(Program).GetMethod("ConvertParcelFeat"); }
}
public MethodInfo ChangeTypeMethod
{
get { return typeof(Program).GetMethod("ChangeType"); }
}
}

Get accessors from PropertyInfo as Func<object> and Action<object> delegates

I need to call properties that are determined at runtime through reflection and they are called at a high frequency. So I am looking for solution with optimal performance, which mean I'd probably avoid reflection. I was thinking of storing the property accessors as Func and Action delegates in a list and then call those.
private readonly Dictionary<string, Tuple<Func<object>, Action<object>>> dataProperties =
new Dictionary<string, Tuple<Func<object>, Action<object>>>();
private void BuildDataProperties()
{
foreach (var keyValuePair in this.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.Name.StartsWith("Data"))
.Select(
p =>
new KeyValuePair<string, Tuple<Func<object>, Action<object>>>(
p.Name,
Tuple.Create(this.GetGetter(p), this.GetSetter(p)))))
{
this.dataProperties.Add(keyValuePair.Key, keyValuePair.Value);
}
}
The question now is, how do I get the accessor delagates as Func and Action delgates for later invokation?
A naïve implementation that still uses reflection for the invocation would look like this:
private Func<object> GetGetter(PropertyInfo info)
{
// 'this' is the owner of the property
return () => info.GetValue(this);
}
private Action<object> GetSetter(PropertyInfo info)
{
// 'this' is the owner of the property
return v => info.SetValue(this, v);
}
How could I implement the above methods without refelections. Would expressions be the fastest way? I have tried using expression like this:
private Func<object> GetGetter(PropertyInfo info)
{
// 'this' is the owner of the property
return
Expression.Lambda<Func<object>>(
Expression.Convert(Expression.Call(Expression.Constant(this), info.GetGetMethod()), typeof(object)))
.Compile();
}
private Action<object> GetSetter(PropertyInfo info)
{
// 'this' is the owner of the property
var method = info.GetSetMethod();
var parameterType = method.GetParameters().First().ParameterType;
var parameter = Expression.Parameter(parameterType, "value");
var methodCall = Expression.Call(Expression.Constant(this), method, parameter);
// ArgumentException: ParameterExpression of type 'System.Boolean' cannot be used for delegate parameter of type 'System.Object'
return Expression.Lambda<Action<object>>(methodCall, parameter).Compile();
}
But here the last line of GetSetter I get the following excpetion if the type of the property is not exactly of type System.Object:
ArgumentException: ParameterExpression of type 'System.Boolean' cannot
be used for delegate parameter of type 'System.Object'
This is my way, it's working fine.
But i dont know it's performance.
public static Func<object, object> GenerateGetterFunc(this PropertyInfo pi)
{
//p=> ((pi.DeclaringType)p).<pi>
var expParamPo = Expression.Parameter(typeof(object), "p");
var expParamPc = Expression.Convert(expParamPo,pi.DeclaringType);
var expMma = Expression.MakeMemberAccess(
expParamPc
, pi
);
var expMmac = Expression.Convert(expMma, typeof(object));
var exp = Expression.Lambda<Func<object, object>>(expMmac, expParamPo);
return exp.Compile();
}
public static Action<object, object> GenerateSetterAction(this PropertyInfo pi)
{
//p=> ((pi.DeclaringType)p).<pi>=(pi.PropertyType)v
var expParamPo = Expression.Parameter(typeof(object), "p");
var expParamPc = Expression.Convert(expParamPo,pi.DeclaringType);
var expParamV = Expression.Parameter(typeof(object), "v");
var expParamVc = Expression.Convert(expParamV, pi.PropertyType);
var expMma = Expression.Call(
expParamPc
, pi.GetSetMethod()
, expParamVc
);
var exp = Expression.Lambda<Action<object, object>>(expMma, expParamPo, expParamV);
return exp.Compile();
}
I think what you need to do is return the Lamda as the correct type, with object as the parameter, however do a conversion within the expression to the correct type before calling the setter:
private Action<object> GetSetter(PropertyInfo info)
{
// 'this' is the owner of the property
var method = info.GetSetMethod();
var parameterType = method.GetParameters().First().ParameterType;
// have the parameter itself be of type "object"
var parameter = Expression.Parameter(typeof(object), "value");
// but convert to the correct type before calling the setter
var methodCall = Expression.Call(Expression.Constant(this), method,
Expression.Convert(parameter,parameterType));
return Expression.Lambda<Action<object>>(methodCall, parameter).Compile();
}
Live example: http://rextester.com/HWVX33724
You need to use a convert method like Convert.ChangeType. The type of property is bool. But the return type of GetSetter methos is object. So you should convert property type that is bool in expression to object.
public static Action<T, object> GetSetter<T>(T obj, string propertyName)
{
ParameterExpression targetExpr = Expression.Parameter(obj.GetType(), "Target");
MemberExpression propExpr = Expression.Property(targetExpr, propertyName);
ParameterExpression valueExpr = Expression.Parameter(typeof(object), "value");
MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(propExpr.Type));
UnaryExpression valueCast = Expression.Convert(convertExpr, propExpr.Type);
BinaryExpression assignExpr = Expression.Assign(propExpr, valueCast);
return Expression.Lambda<Action<T, object>>(assignExpr, targetExpr, valueExpr).Compile();
}
private static Func<T, object> GetGetter<T>(T obj, string propertyName)
{
ParameterExpression arg = Expression.Parameter(obj.GetType(), "x");
MemberExpression expression = Expression.Property(arg, propertyName);
UnaryExpression conversion = Expression.Convert(expression, typeof(object));
return Expression.Lambda<Func<T, object>>(conversion, arg).Compile();
}
LIVE DEMO
EDIT:
public class Foo
{
#region Fields
private readonly Dictionary<string, Tuple<Func<Foo, object>, Action<Foo, object>>> dataProperties = new Dictionary<string, Tuple<Func<Foo, object>, Action<Foo, object>>>();
#endregion
#region Properties
public string Name { get; set; }
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
public int ID { get; set; }
#endregion
#region Methods: public
public void BuildDataProperties()
{
foreach (
var keyValuePair in
GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.Name.StartsWith("Data"))
.Select(p => new KeyValuePair<string, Tuple<Func<Foo, object>, Action<Foo, object>>>(p.Name, Tuple.Create(GetGetter(this, p.Name), GetSetter(this, p.Name))))) {
dataProperties.Add(keyValuePair.Key, keyValuePair.Value);
}
}
#endregion
#region Methods: private
private Func<T, object> GetGetter<T>(T obj, string propertyName)
{
ParameterExpression arg = Expression.Parameter(obj.GetType(), "x");
MemberExpression expression = Expression.Property(arg, propertyName);
UnaryExpression conversion = Expression.Convert(expression, typeof(object));
return Expression.Lambda<Func<T, object>>(conversion, arg).Compile();
}
private Action<T, object> GetSetter<T>(T obj, string propertyName)
{
ParameterExpression targetExpr = Expression.Parameter(obj.GetType(), "Target");
MemberExpression propExpr = Expression.Property(targetExpr, propertyName);
ParameterExpression valueExpr = Expression.Parameter(typeof(object), "value");
MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(propExpr.Type));
UnaryExpression valueCast = Expression.Convert(convertExpr, propExpr.Type);
BinaryExpression assignExpr = Expression.Assign(propExpr, valueCast);
return Expression.Lambda<Action<T, object>>(assignExpr, targetExpr, valueExpr).Compile();
}
#endregion
}
You can get a value from dictionary like below :
var t = new Foo { ID = 1, Name = "Bla", Data1 = "dadsa"};
t.BuildDataProperties();
var value = t.dataProperties.First().Value.Item1(t);
LIVE DEMO

Create an expression that will evaluate the result of a delegate and return the consequent or the alternate as the case may be

I want to retrieve the value of a property using a getter expression , but within that expression I want to evaluate a predicate and only return the value of the property if predicate evaluates to false, otherwise return a constant.
Something along the lines of (partially using code from here):
Expression<Func<U, bool>> exp = FuncToExpression(predicate);
var instance = Expression.Parameter(propertyInfo.DeclaringType, "instance");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
var getLamba = Expression.Lambda(convert, instance);
var evaluate = Expression.Condition(exp, getLamba, Expression.Constant(alternate));
var lambda = Expression.Lambda(evaluate, instance);
return (Func<T, object>)lambda.Compile();
Any help here would be appreciated
Edit
More detail as per Jon's comment:
I am getting the following error on the evaluate variable :
{"Argument must be boolean"}
This is the FuncToExpression method :
private static Expression<Func<U, bool>> FuncToExpression<U>(Func<U, bool> predicate)
{
return argument => predicate(argument);
}
Edit 2
Complete Sample:
public class Test
{
public static void Main(string[] args)
{
TestPredicate test = new TestPredicate();
test.Number = 11;
Func<TestPredicate, object> callDelegate;
PropertyInfo info = typeof(TestPredicate).GetProperties().Where(a => a.Name == "Number").FirstOrDefault();
Func<int, bool> f = (x => x > 10 ? true : false);
if (info != null)
{
callDelegate = CreateValueGetDelegate<TestPredicate, int, int>(info, f, -1);
var item = (int) callDelegate(test);
Console.WriteLine(item); // expecting -1 here
}
Console.Read();
}
private static Func<T,object> CreateValueGetDelegate<T,U, S>(PropertyInfo propertyInfo, Func<U, bool> predicate, S alternate)
{
if (typeof(T) != propertyInfo.DeclaringType)
{
throw new ArgumentException();
}
Expression<Func<U, bool>> exp = FuncToExpression(predicate);
var instance = Expression.Parameter(propertyInfo.DeclaringType, "instance");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
var getLamba = Expression.Lambda(convert, instance);
var evaluate = Expression.Condition(exp, getLamba, Expression.Constant(alternate));
var lambda = Expression.Lambda(evaluate, instance);
return (Func<T, object>)lambda.Compile();
}
private static Expression<Func<U, bool>> FuncToExpression<U>(Func<U, bool> predicate)
{
return argument => predicate(argument);
}
public class TestPredicate
{
public int Number { get; set; }
}
}
It would have helped if you'd said what currently going wrong, but I think you just need to get rid of the first Lambda call. I've made a few changes to the variable names too:
Expression<Func<U, bool>> test = FuncToExpression(predicate);
var parameter = Expression.Parameter(propertyInfo.DeclaringType, "instance");
var property = Expression.Property(parameter, propertyInfo);
var trueOption = Expression.TypeAs(property, typeof(object));
var falseOption = Expression.Constant(alternative);
var conditional = Expression.Condition(test, trueOption, falseOption);
var lambda = Expression.Lambda<Func<T, object>>(conditional, parameter);
return lambda.Compile();
If this doesn't work, please let us know in what way - ideally editing a short but complete sample program into your question.
This extension method will allow you to provide a selector (get the property), validator (validate the property) and a default value:
public static P GetValueOrDefault<T, P>(this T item, Func<T, P> selector, Func<P, bool> validator, P defaultValue)
{
if (item == null)
return defaultValue;
P value = selector(item);
if (validator == null || !validator(value))
return defaultValue;
return value;
}

How do I set a field value in an C# Expression tree?

Given:
FieldInfo field = <some valid string field on type T>;
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
How do I compile a lambda expression to set the field on the "target" parameter to "value"?
.Net 4.0 : now that there's Expression.Assign, this is easy to do:
FieldInfo field = typeof(T).GetField("fieldName");
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
// Expression.Property can be used here as well
MemberExpression fieldExp = Expression.Field(targetExp, field);
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp);
var setter = Expression.Lambda<Action<T, string>>
(assignExp, targetExp, valueExp).Compile();
setter(subject, "new value");
.Net 3.5 : you can't, you'll have to use System.Reflection.Emit instead:
class Program
{
class MyObject
{
public int MyField;
}
static Action<T,TValue> MakeSetter<T,TValue>(FieldInfo field)
{
DynamicMethod m = new DynamicMethod(
"setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(Program));
ILGenerator cg = m.GetILGenerator();
// arg0.<field> = arg1
cg.Emit(OpCodes.Ldarg_0);
cg.Emit(OpCodes.Ldarg_1);
cg.Emit(OpCodes.Stfld, field);
cg.Emit(OpCodes.Ret);
return (Action<T,TValue>) m.CreateDelegate(typeof(Action<T,TValue>));
}
static void Main()
{
FieldInfo f = typeof(MyObject).GetField("MyField");
Action<MyObject,int> setter = MakeSetter<MyObject,int>(f);
var obj = new MyObject();
obj.MyField = 10;
setter(obj, 42);
Console.WriteLine(obj.MyField);
Console.ReadLine();
}
}
Setting a field is, as already discussed, problematic. You can can (in 3.5) a single method, such as a property-setter - but only indirectly. This gets much easier in 4.0, as discussed here. However, if you actually have properties (not fields), you can do a lot simply with Delegate.CreateDelegate:
using System;
using System.Reflection;
public class Foo
{
public int Bar { get; set; }
}
static class Program
{
static void Main()
{
MethodInfo method = typeof(Foo).GetProperty("Bar").GetSetMethod();
Action<Foo, int> setter = (Action<Foo, int>)
Delegate.CreateDelegate(typeof(Action<Foo, int>), method);
Foo foo = new Foo();
setter(foo, 12);
Console.WriteLine(foo.Bar);
}
}
private static Action<object, object> CreateSetAccessor(FieldInfo field)
{
DynamicMethod setMethod = new DynamicMethod(field.Name, typeof(void), new[] { typeof(object), typeof(object) });
ILGenerator generator = setMethod.GetILGenerator();
LocalBuilder local = generator.DeclareLocal(field.DeclaringType);
generator.Emit(OpCodes.Ldarg_0);
if (field.DeclaringType.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, field.DeclaringType);
generator.Emit(OpCodes.Stloc_0, local);
generator.Emit(OpCodes.Ldloca_S, local);
}
else
{
generator.Emit(OpCodes.Castclass, field.DeclaringType);
generator.Emit(OpCodes.Stloc_0, local);
generator.Emit(OpCodes.Ldloc_0, local);
}
generator.Emit(OpCodes.Ldarg_1);
if (field.FieldType.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, field.FieldType);
}
else
{
generator.Emit(OpCodes.Castclass, field.FieldType);
}
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
return (Action<object, object>)setMethod.CreateDelegate(typeof(Action<object, object>));
}
Actually there is a way to set properties and fields with Expression Trees in .NET 3.5. It is may be the only option for some PCL profiles that do not support Delegate.CreateDelegate (besides the Reflection.Emit):
For field the trick is passing field as ref parameter,
e.g. SetField(ref holder.Field, "NewValue");
The property (as already pointed by Marc) can be set by reflecting and calling its setter method.
The full proof of concept is provided below as NUnit test fixture.
[TestFixture]
public class CanSetPropAndFieldWithExpressionTreeInNet35
{
class Holder
{
public int Field;
public string Prop { get; set; }
}
public static class FieldAndPropSetter
{
public static T SetField<T, TField>(T holder, ref TField field, TField value)
{
field = value;
return holder;
}
public static T SetProp<T>(T holder, Action<T> setProp)
{
setProp(holder);
return holder;
}
}
[Test]
public void Can_set_field_with_expression_tree_in_Net35()
{
// Shows how expression could look like:
Func<Holder, Holder> setHolderField = h => FieldAndPropSetter.SetField(h, ref h.Field, 111);
var holder = new Holder();
holder = setHolderField(holder);
Assert.AreEqual(111, holder.Field);
var holderType = typeof(Holder);
var field = holderType.GetField("Field");
var fieldSetterMethod =
typeof(FieldAndPropSetter).GetMethod("SetField")
.MakeGenericMethod(holderType, field.FieldType);
var holderParamExpr = Expression.Parameter(holderType, "h");
var fieldAccessExpr = Expression.Field(holderParamExpr, field);
// Result expression looks like: h => FieldAndPropSetter.SetField(h, ref h.Field, 222)
var setHolderFieldExpr = Expression.Lambda<Func<Holder, Holder>>(
Expression.Call(fieldSetterMethod, holderParamExpr, fieldAccessExpr, Expression.Constant(222)),
holderParamExpr);
var setHolderFieldGenerated = setHolderFieldExpr.Compile();
holder = setHolderFieldGenerated(holder);
Assert.AreEqual(222, holder.Field);
}
[Test]
public void Can_set_property_with_expression_tree_in_Net35()
{
// Shows how expression could look like:
Func<Holder, Holder> setHolderProp = h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "ABC");
var holder = new Holder();
holder = setHolderProp(holder);
Assert.AreEqual("ABC", holder.Prop);
var holderType = typeof(Holder);
var prop = holderType.GetProperty("Prop");
var propSet = prop.GetSetMethod();
var holderParamExpr = Expression.Parameter(holderType, "h");
var callSetPropExpr = Expression.Call(holderParamExpr, propSet, Expression.Constant("XXX"));
var setPropActionExpr = Expression.Lambda(callSetPropExpr, holderParamExpr);
var propSetterMethod = typeof(FieldAndPropSetter).GetMethod("SetProp").MakeGenericMethod(holderType);
// Result expression looks like: h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "XXX")
var setHolderPropExpr = Expression.Lambda<Func<Holder, Holder>>(
Expression.Call(propSetterMethod, holderParamExpr, setPropActionExpr),
holderParamExpr);
var setHolderPropGenerated = setHolderPropExpr.Compile();
holder = setHolderPropGenerated(holder);
Assert.AreEqual("XXX", holder.Prop);
}
}
I once made this class. Perhaps it helps:
public class GetterSetter<EntityType,propType>
{
private readonly Func<EntityType, propType> getter;
private readonly Action<EntityType, propType> setter;
private readonly string propertyName;
private readonly Expression<Func<EntityType, propType>> propertyNameExpression;
public EntityType Entity { get; set; }
public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
{
Entity = entity;
propertyName = GetPropertyName(property_NameExpression);
propertyNameExpression = property_NameExpression;
//Create Getter
getter = propertyNameExpression.Compile();
// Create Setter()
MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
setter = (Action<EntityType, propType>)
Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
}
public propType Value
{
get
{
return getter(Entity);
}
set
{
setter(Entity, value);
}
}
protected string GetPropertyName(LambdaExpression _propertyNameExpression)
{
var lambda = _propertyNameExpression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.Name;
}
test:
var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
gs.Value = true;
var result = gs.Value;
Just for completeness here is the getter:
public static IEnumerable<Func<T, object>> GetTypeGetters<T>()
{
var fields = typeof (T).GetFields();
foreach (var field in fields)
{
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
UnaryExpression boxedFieldExp = Expression.Convert(Expression.Field(targetExp, field), typeof(object));
yield return Expression.Lambda<Func<T,object>>(boxedFieldExp, targetExp).Compile();
}
}

Categories

Resources