CSharp -> Property getter expression does not work with Condition - c#

I've got the following Code:
public static Func<object, string> GetPropGetter(Type objectType, string propertyName)
{
ParameterExpression paramExpression = Expression.Parameter(typeof(object), "value");
Expression e = Expression.Convert(paramExpression, objectType);
foreach (var name in propertyName.Split('.'))
{
e = Expression.Property(e, name);
}
Expression propertyGetterExpression = Expression.Call(e, typeof(object).GetMethod("ToString", Type.EmptyTypes));
Func<object, string> result =
Expression.Lambda<Func<object, string>>(propertyGetterExpression, paramExpression).Compile();
return result;
}
This works if the Property is not null. For this check I changed the Code to:
public static Func<object, string> GetPropGetter(Type objectType, string propertyName)
{
ParameterExpression paramExpression = Expression.Parameter(typeof(object), "value");
Expression e = Expression.Convert(paramExpression, objectType);
foreach (var name in propertyName.Split('.'))
{
e = Expression.Property(e, name);
}
Expression propertyGetterExpression = Expression.IfThenElse(Expression.Equal(Expression.Default((e as MemberExpression).Type), e), Expression.Constant(""), Expression.Call(e, typeof(object).GetMethod("ToString", Type.EmptyTypes)));
Func<object, string> result =
Expression.Lambda<Func<object, string>>(propertyGetterExpression, paramExpression).Compile();
return result;
}
Now I got the Exception: ArgumentException, the Expression of Type void could not be used for the return value of string!

It may not be the only thing that you need to do, but I think you want to use Expression.Condition rather than Expression.IfThenElse.
Currently you've got the equivalent of:
if (condition) {
default(...);
} else {
property-getters
}
without any return. (As noted in the documentation, the overall type of the expression returned by IfThenElse is Void.)
You really want:
return condition ? default(...) : property-getters;
The latter is what Expression.Condition represents.

Related

Build a Generic Expression Tree .NET Core

Hello Community i am aware of this might be a possible duplicate.
How do I dynamically create an Expression<Func<MyClass, bool>> predicate from Expression<Func<MyClass, string>>?
https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/
How to create a Expression.Lambda when a type is not known until runtime?
Creating expression tree for accessing a Generic type's property
There are obviously too many resources.
I am still confused though.
Could someone provide a clearer picture of what is happening in the below code.
Below i have provided some comments to help my understanding.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = Expression.Constant(true); //Casting error
if (string.IsNullOrEmpty(parameters))
return finalExpression;
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly we TODO: Better way for error handling
//Property might be containment property e.g T.TClass.PropName
Expression nameProperty = Expression.Property(argParam, parsedParameter.propertyName);
//Value to filter against
var value = Expression.Constant(parsedParameter.value);
Expression comparison;
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
comparison = Expression.Equal(nameProperty, value);
break;
//goes on for NotEquals, GreaterThan etc
}
finalExpression = Expression.Lambda(comparison, argParam);// Casting error
}
return finalExpression;
}
The above obviously is not working.
This is returned to linq query like this IEnumerable<SomeModel>.Where(ParseParametersToFilter.Compile())
I understand my mistake is a casting mistake.
How could i fix this?
After #Jeremy Lakeman answer i updated my code to look like this. Although the ViewModel i am using is quite complex. I have provided a small preview at the end.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = t => true;
if (string.IsNullOrEmpty(parameters))
return finalExpression;
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
Expression body = Expression.Constant(true);
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly TODO: Better way for error handling
//Property might be containment property e.g T.TClass.PropName
Expression nameProperty = Expression.Property(argParam, parsedParameter.propertyName);
//Value to filter against
var value = Expression.Constant(parsedParameter.value);
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
}
body = Expression.AndAlso(body, argParam);
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
But now i get the following Exceptions
When i pass the Status as property parameter:
The binary operator Equal is not defined for the types 'model.StatusEnum' and 'System.String'.
When i pass the User.FriendlyName parameter:
Instance property 'User.FriendlyName' is not defined for type 'model.ReportViewModel'
Parameter name: propertyName
Here is how my view model looks like!
public class ReportViewModel
{
public StatusEnum Status {get;set;}
public UserViewModel User {get;set;}
}
public enum StatusEnum
{
Pending,
Completed
}
public class UserViewModel
{
public string FriendlyName {get;set;}
}
So you're trying to turn something like "a==1,b==3" into viewModel => viewModel.a == 1 && viewModel.b == 3?
I think you're already pretty close, you just need add the && (or ||), and always create a lambda;
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
Expression body = Expression.Constant(true);
if (!string.IsNullOrEmpty(parameters)){
body = parameters.Split(",")
.Select(param => {
var parsedParameter = ParseParameter(param);
// ... as above, turn param into a comparison expression ...
return comparison;
})
.Aggregage((l,r) => Expression.AndAlso(l, r));
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
And if this is for passing to entity framework, don't compile it or you'll only be able to evaluate it client side.
Here is what i came up with and works pretty well, from my tests today.
Some refactoring may be needed. I am open to suggestions.
Please make sure to check the comments inside the code.
private void ConvertValuePropertyType(Type type, string value, out dynamic converted)
{
// Here i convert the value to filter to the necessary type
// All my values come as strings.
if (type.IsEnum)
converted = Enum.Parse(type, value);
else if (type == typeof(DateTime))
converted = DateTime.Parse(value);
else if (type is object)
converted = value;
else
throw new InvalidCastException($"Value was not converted properly {nameof(value)} {nameof(type)}");
}
private MemberExpression GetContainmentMember(ParameterExpression parameterExpression, string propertyName)
{
//propertName looks like this User.FriendlyName
//So we have to first take T.User from the root type
// Then the Name property.
// I am not sure how to make this work for any depth.
var propNameArray = propertyName.Split(".");
if (propNameArray.Length > 1)
{
MemberExpression member = Expression.Property(parameterExpression, propNameArray[0]);
return Expression.PropertyOrField(member, propNameArray[1]);
}
else
{ //This needs to make sure we retrieve containment
return Expression.Property(parameterExpression, propertyName);
}
}
// ***************************************************************
// This is the core method!
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression body = Expression.Constant(true);
ParameterExpression argParam = Expression.Parameter(typeof(T), nameof(T));
if (string.IsNullOrEmpty(parameters))
return Expression.Lambda<Func<T, bool>>(body, argParam); // return empty filter
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly, do not fail continue
//Get model
//Get property name
//Property might be containment property e.g T.TClass.PropName
//Value to filter against
MemberExpression nameProperty = GetContainmentMember(argParam, parsedParameter.propertyName);
//Convert property value according to property name
Type propertyType = GetPropertyType(typeof(T), parsedParameter.propertyName);
ConvertValuePropertyType(propertyType, parsedParameter.value, out object parsedValue);
var value = Expression.Constant(parsedValue);
switch (parsedParameter.operation)
{
//What operation did the parser retrieve
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
default:
break;
}
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
This worked very good so far.

Dynamically create lambda search on IQueryable on nested objects

I'm trying to build a dynamic search on nested objects, which will later be sent to EF and SQL Server. So far, I'm able to search on all properties of the first object. Here's a very simplified version:
public class User
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
}
public class MyClass<TEntity> where TEntity : class {
public IQueryable<TEntity> applySearch(IQueryable<TEntity> originalList, string propName, string valueToSearch) {
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
PropertyInfo propertyInfo = typeof(TEntity).GetProperty(propName);
MemberExpression member = Expression.MakeMemberAccess(parameterExpression, propertyInfo);
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(valueToSearch)), parameterExpression);
return originalList.Where(expression);
}
}
When propName = "Name" everything is fine, but when propName = "Address.City", the propertyInfo is null, and I get this error on the member assignment line:
System.ArgumentNullException: Value cannot be null
I was able to obtain the propertyInfo of the nested property using the solution from this answer:
PropertyInfo propertyInfo = GetPropertyRecursive(typeof(TEntity), propName);
...
private PropertyInfo GetPropertyRecursive(Type baseType, string propertyName)
{
string[] parts = propertyName.Split('.');
return (parts.Length > 1)
? GetPropertyRecursive(baseType.GetProperty(parts[0]).PropertyType, parts.Skip(1).Aggregate((a, i) => a + "." + i))
: baseType.GetProperty(propertyName);
}
But then I get this error on member assignment:
System.ArgumentException: Property 'System.String City' is not defined for type 'User'
This should point to Address instead of User, but I don't know if I'm on right track here, I mean, should I change parameterExpression now?
How can I make a dynamic search on nested objects, so that this can be turned into a lambda expression and later sent to SQL?
After Kobi's advice and a lot of trial and error, I finally got this working. This uses the Universal PredicateBuilder. Here it is:
public class MyClass<TEntity> where TEntity : class
{
public IQueryable<TEntity> ApplySearch(IQueryable<TEntity> originalList, string valueToSearch, string[] columnsToSearch)
{
Expression<Func<TEntity, bool>> expression = null;
foreach (var propName in columnsToSearch)
{
Expression<Func<TEntity, bool>> lambda = null;
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
string[] nestedProperties = propName.Split('.');
Expression member = parameterExpression;
foreach (string prop in nestedProperties)
{
member = Expression.PropertyOrField(member, prop);
}
var canConvert = CanConvertToType(valueToSearch, member.Type.FullName);
if (canConvert)
{
var value = ConvertToType(valueToSearch, member.Type.FullName);
if (member.Type.Name == "String")
{
ConstantExpression constant = Expression.Constant(value);
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(member, mi, constant);
lambda = Expression.Lambda<Func<TEntity, bool>>(call, parameterExpression);
}
else
{
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(value)), parameterExpression);
}
}
if (lambda != null)
{
if (expression == null)
{
expression = lambda;
}
else
{
expression = expression.Or(lambda);
}
}
}
if (expression != null)
{
return originalList.Where(expression);
}
return originalList;
}
}
private bool CanConvertToType(object value, string type)
{
bool canConvert;
try
{
var cValue = ConvertToType(value, type);
canConvert = true;
}
catch
{
canConvert = false;
}
return canConvert;
}
private dynamic ConvertToType(object value, string type)
{
return Convert.ChangeType(value, Type.GetType(type));
}
Warning in advance - I'm not building the expression, just inspecting its structure.
When I need to dynamically create Expressions, I find it useful to inspect an Expression and copy its structure:
Expression<Func<User, string>> getCity = user => user.Address.City;
Now you can simply debug it, for example in the immediate window (ctrlalti here):
getCity
{user => user.Address.City}
Body: {user.Address.City}
CanReduce: false
DebugView: ".Lambda #Lambda1<System.Func`2[ConsoleApplication1.User,System.String]>(ConsoleApplication1.User $user) {\r\n ($user.Address).City\r\n}"
Name: null
NodeType: Lambda
Parameters: Count = 1
ReturnType: {Name = "String" FullName = "System.String"}
TailCall: false
Here we can see getCity is a Lambda with one parameter. Let's inspect it's body:
getCity.Body
{user.Address.City}
CanReduce: false
DebugView: "($user.Address).City"
Expression: {user.Address}
Member: {System.String City}
NodeType: MemberAccess
Type: {Name = "String" FullName = "System.String"}
getCity.Body is a member access - it accesses the member City of the Expression user.Address. Technically that's a PropertyExpression, which is an internal class so we can't even cast to it, but that's OK.
Finally, let's look at that inner expression:
((MemberExpression)getCity.Body).Expression
{user.Address}
CanReduce: false
DebugView: "$user.Address"
Expression: {user}
Member: {ConsoleApplication1.Address Address}
NodeType: MemberAccess
Type: {Name = "Address" FullName = "ConsoleApplication1.Address"}
That's just user.Address.
Now we can build an identical expression:
var addressProperty = typeof (User).GetProperty("Address");
var cityProperty = typeof(Address).GetProperty("City");
var userParameter = Expression.Parameter(typeof (User), "user");
var getCityFromUserParameter = Expression.Property(Expression.Property(userParameter, addressProperty), cityProperty);
var lambdaGetCity = Expression.Lambda<Func<User, string>>(getCityFromUserParameter, userParameter);
Expression.MakeMemberAccess works too, instead of Expression.Property.
Obviously, you'd need to build your expression in a loop, and more dynamically, but the structure is the same.
It might be worth taking a look at Linqkit's predicate builder...
http://www.albahari.com/nutshell/predicatebuilder.aspx
I'd also take a look at Entity SQL...
https://msdn.microsoft.com/en-us/library/vstudio/bb387145(v=vs.100).aspx
You might be reinventing a wheel with the code you're writing.
Also I should comment, in terms of the SQL Server plan caching, unless you have no other choice I wouldn't dynamically build queries. You're better off creating a single query that handles all your cases that SQL Server can cache a plan for, your queries will run a lot slower if every time they're executed no plan is hit in SQL Server's plan cache.

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

Reading Properties of an Object with Expression Trees

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.

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

Categories

Resources