I'm storing an update operation as thus:
class Update
{
public Expression MemberExpression { get; set; }
public Type FieldType { get; set; }
public object NewValue { get; set; }
}
For example:
var myUpdate = new Update
{
MemberExpression = (MyType o) => o.LastModified,
FieldType = typeof(DateTime?),
NewValue = (DateTime?)DateTime.Now
}
Then I'm trying to apply this update later (simplified):
var lambda = myUpdate.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
var prop = memberExpr.Member as PropertyInfo;
prop.SetValue(item, myUpdate.Value);
However, myUpdate.Value is a DateTime, not a DateTime?. This is because when you cast a nullable to an object, it either becomes null or boxes the value type.
Since (DateTime?)myUpdate.Value would work to get it back to the correct type, I tried to simulate this compile-time construct using Convert.ChangeType. However, it says that casting from DateTime to DateTime? is not possible.
What approach can I use here to get the object back into its original type?
Is there any way to store a nullable types, regular structs and regular objects in a single field, and get the exact thing stored into it back again?
If you know the type of your Expression when you create myUpdate, SetInfo() will properly set your data, even if boxing/unboxing.
public class Update
{
public Expression MemberExpression { get; set; }
public Type ClassType { get; set; }
public object NewValue { get; set; }
}
public class MyType
{
public DateTime? LastModified { get; set; }
}
void Main()
{
var myUpdate = new Update
{
MemberExpression =
(Expression<Func<MyType, DateTime?>>)((MyType o) => o.LastModified),
ClassType = typeof(MyType),
NewValue = DateTime.Now
};
// At a later point where we do not want to instantiate via strong typing
var item = Activator.CreateInstance(myUpdate.ClassType);
var lambda = myUpdate.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
var prop = memberExpr.Member as PropertyInfo;
prop.SetValue(item, myUpdate.NewValue);
item.Dump();
}
This properly outputs a DateTime value corresponding to when it was created without an exception.
Maybe you can use a generic class?
class Update<TField>
{
public Expression<Func<MyType, TField>> MemberExpression { get; set; }
public TField NewValue { get; set; }
}
Not sure if you can just use a plain Func<MyType, TField> delegate instead of the expression tree, for your use.
I was able to solve using expressions and no reflection too:
var lambda = update.MemberExpression as LambdaExpression;
var memberExpr = lambda.Body as MemberExpression;
if (memberExpr == null)
{
throw new NotSupportedException("Field expression must be a member expression");
}
// do a cast - this ensures nullable types work, for instance
var cast = Expression.Convert(Expression.Constant(update.Value), update.FieldType);
// assign the target member with the cast value
var assignment = Expression.Assign(memberExpr, cast);
// build a new lambda, no return type, which does the assignment
var newLambda = Expression.Lambda(typeof(Action<T>), assignment, lambda.Parameters[0]);
// compile to something we can invoke, and invoke
var compiled = (Action<T>)newLambda.Compile();
compiled(item);
And voila, the item is modified :)
Related
I want to create an expression for converting a property from an Enum to an Int.
I want to use DynamicExpressionParser.ParseLambda to parse nested properties:
public enum MyEnum { A, B };
public class OuterClass
{
public InnerClass Inner { get; set; }
public MyEnum Enum { get; set; }
}
public class InnerClass
{
public MyEnum Enum { get; set; }
}
DynamicExpressionParser.ParseLambda<OuterClass, int>(null, true, "Inner.Enum");
But this will throw an exception:
Expression of type 'Int32' expected (at index 0)
How can I convert the Enum to an integer inside the expression?
It works with the following method, but i cannot use nested properties there, so it will only work when I place the "Enum" property on the OuterClass:
public static Expression<Func<T, TProperty>> GetExpression<T, TProperty>(string propertyName)
{
// x =>
var parameter = Expression.Parameter(typeof(T));
// x.Name
var mapProperty = Expression.Property(parameter, propertyName);
// (object)x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(TProperty));
// x => (object)x.Name
return Expression.Lambda<Func<T, TProperty>>(convertedExpression, parameter);
}
GetExpression<OuterClass, int>("Enum");
I found a solution:
// Create expression for it-type OuterClass. Pass null for result type which will result in an expression with the result type of the actual enum type.
var enumExpression = DynamicExpressionParser.ParseLambda(typeof(OuterClass), null, "Inner.Enum");
// Convert the result type of the body to int which will result in a converted expression
var convertedBodyExpression = Expression.Convert(enumExpression.Body, typeof(int));
// Create the final expression
var expression = Expression.Lambda<Func<OuterClass, int>>(convertedBodyExpression, enumExpression.Parameters);
I have the following method:
public bool Method(object obj){
var objType = obj.GetType();
//do some reflection to get obj properties for some logic
...
if(someCondition) {
obj = null;
return false;
}
if(condition2){ return true;}
return false;
}
Method call:
var someObj = SomeObj();
Method(someObj.someProperty);
public class SomeObj : IEntity{
public SomeProperty someProperty {get;set;} //this needs to be set to null in some conditions
}
I would like to have a general method that takes in all types of Entity objects, that's why it's of "object" type. Then I would like to get its type, and go through all the properties to perform some logic. In the end, based on certain condition, I would like to set the object passed in to null. However, the problem is that the object passed in is always a property of some other Entity object, so I can't set it to ref. How do I get around this?
Since you've already used reflection to determine property info, performance most likely isn't your top concern. You can pass the object and the prop name (since you used reflection to get the obj properties already) and set it to null.
var a = new TestA();
a.Prop.Id = 1;
Method(a, "Prop");
Console.Write(a.Prop);
public bool Method(object obj, string propName)
{
// if condition is met
obj.GetType().GetProperty(propName).SetValue(obj, null);
return false;
}
public class TestA
{
public TestB Prop { get; set; } = new TestB();
}
public class TestB
{
public int Id { get; set; }
}
In the above example, a.Prop will be null.
Pass an expression
You aren't currently passing a property into Method. You are passing the value of the property. The method has no idea what the original property is and therefore cannot set it to null.
To get around this, you can pass in an Expression representing the property instead of the property's value. Then your method can parse the expression and do whatever is needed, including reading and setting the value.
So instead of doing this
var someObj = new SomeObj();
Method(someObj.someProperty); //Obtains the value of the property and passes it
The caller would do this:
var someObj = new SomeObj();
Method(() => someObj.someProperty); //Passes an expression representing the property
To modify Method so that it can work this way, you have to do a little bit of expression parsing, but not much:
static public bool Method<T>(Expression<Func<T>> expression) where T : class
{
var memberExp = (MemberExpression)expression.Body;
var propertyInfo = memberExp.Member as PropertyInfo;
var targetObject = Expression.Lambda<Func<object>>(memberExp.Expression).Compile()();
Console.WriteLine("Old value is {0}", propertyInfo.GetValue(targetObject));
if (someCondition1)
{
propertyInfo.SetValue(targetObject, null); //Set it to null
return false;
}
}
If you plan to call this frequently and you're worried about the performance of Compile(), you can avoid the compilation with slightly more code:
static public bool Method<T>(Expression<Func<T>> expression)
{
var memberExp = (MemberExpression)expression.Body;
var propertyInfo = memberExp.Member as PropertyInfo;
var propertyExpression = memberExp.Expression as MemberExpression;
var targetObject = ((FieldInfo)propertyExpression.Member).GetValue((propertyExpression.Expression as ConstantExpression).Value);
Console.WriteLine("Old value is {0}", propertyInfo.GetValue(targetObject));
if (SomeCondition1)
{
propertyInfo.SetValue(targetObject, null);
return false;
}
}
I am trying to get MemberInfo for a child property from a MemberExpression. I have found ways to get the full name of the nested type, but not a way to get the whole MemberInfo of the nested type. Here is a quick example of the scenario I am talking about:
Some simple models (the goal is to eventually get the MemberInfo of the Data property of the Child class)
public class Parent
{
public int ParentProperty { get; set; }
public Child Child { get; set; }
}
public class Child
{
public string Data { get; set; }
}
The lambda expression
Expression<Func<Parent,string>> func = new Func<Parent, string>(p =>
{
return p.Child.Data;
});
Code used to get the MemberInfo from the lambda expression.
internal static MemberInfo FindMemberInfoFromLambda(LambdaExpression lambda)
{
var expression = (Expression) lambda;
var flag = false;
while (!flag)
{
switch (expression.NodeType)
{
case ExpressionType.Convert:
expression = ((UnaryExpression) expression).Operand;
continue;
case ExpressionType.Lambda:
expression = ((LambdaExpression) expression).Body;
continue;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
if (memberExpression.Expression.NodeType == ExpressionType.Parameter ||
memberExpression.Expression.NodeType == ExpressionType.Convert)
return memberExpression.Member;
throw new Exception();
default:
flag = true;
continue;
}
}
throw new Exception();
}
This code works great if I were trying to get the ParentProperty of the Parent class, but when I try to get the MemberInfo of the Data property of the Child class, it does not work. I have seen a few StackOverflow questions posted on getting the full name of the child property, but nothing on getting the whole MemberInfo of it. Has anyone done this before or can help point me in the right direction?
The expression you get is MemberExpression, you can grab its Member property directly:
class Program
{
class Parent
{
public int A { get; set; }
public Child Child { get; set; }
}
class Child
{
public string Data { get; set; }
}
public static MemberInfo GetMemberInfo(LambdaExpression exp)
{
var body = exp.Body as MemberExpression;
return body.Member;
}
static void Main(string[] args)
{
Expression<Func<Parent, string>> func1 = p => p.Child.Data;
Console.WriteLine(GetMemberInfo(func1));
Expression<Func<Parent, int>> func2 = p => p.A;
Console.WriteLine(GetMemberInfo(func2));
}
}
Output:
System.String Data
Int32 A
You must be using Expression instead of just Func
In your code in the MemberAccess section you are checking if the member is from the parameter, in this case Parent. If you remove that check then you will get the member for Data
Change this section
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
if (memberExpression.Expression.NodeType == ExpressionType.Parameter ||
memberExpression.Expression.NodeType == ExpressionType.Convert)
return memberExpression.Member;
throw new Exception();
To
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
return memberExpression.Member;
I don't know why you had the guard for the parameter, if you need it in certain cases then you can create a different method or pass in a parameter.
public Expression GetValue<T>(T source, PropertyInfo property)
{
ParameterExpression fieldName = Expression.Parameter(pi.PropertyType, pi.Name);
Expression fieldExpr = Expression.PropertyOrField(Expression.Constant(source), pi.Name);
var exp = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), fieldExpr.Type),
fieldExpr,
fieldName);
return exp;
}
Here is my calling function
public MvcHtmlString RenderModel(T model)
{
foreach (var prop in typeof(T).GetProperties())
{
var x = InputExtensions.TextBoxFor(h, (dynamic) GetValue<T>(model, prop));
}
return new MvcHtmlString(string.Empty);
}
Here is an example class:
public class Test
{
public int? ID { get; set; }
public string Name { get; set; }
public DateTime? Date{ get; set; }
}
I adopted GetValue from part of:
Member Expression cannot convert to object from nullable decimal
which seems to work quite well until you reach properties that are not of type object I get this error:
ParameterExpression of type 'System.Nullable`1[System.Int32]' cannot be used for delegate parameter of type 'Test'
I'm not really sure where I've gone wrong here, and what that error even means
If I understand correctly, you need property access lambda. Here is the correct version of GetValue<T> method:
public Expression GetValue<T>(T source, PropertyInfo pi)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
body = Expression.PropertyOrField(body, pi.Name);
return Expression.Lambda(body, param);
}
If you want to make an Expression by providing a delegate (that in your question it is Func<T,..> ) then you have to provide the delegate type, body expression and parameter expressions too:
public Expression GetValue<T>(T source, PropertyInfo property)
{
return Expression.Lambda(
delegateType: typeof(Func<,>).MakeGenericType(typeof(T), pi.PropertyType),
body: Expression.PropertyOrField(Expression.Parameter(typeof(T), "x"), pi.Name),
parameters: Expression.Parameter(typeof(T), "x"));
}
I want to get the LINQ Expression of the Reference Property
I need to get the Lambda Expression as groupCol=>groupCol.Role.Name
I have tried with the expression but not succeeded , this will work with groupCol=>groupCol.MenuText but not with reference types
var menu = Expression.Parameter(typeof(Menu), "groupCol");
// getting Role.Name' is not a member of type exception
var menuProperty = Expression.PropertyOrField(menu, property);
var lambda = Expression.Lambda<Func<Menu, string>>(menuProperty, menu);
public class Menu
{
public string MenuText {get;set;}
public Role Role {get;set;}
public string ActionName {get;set;}
}
public class Role
{
public string Name {get;set;}
}
Thanks in Advance
You need to do this one property at a time:
private static Expression<Func<Menu, string>> GetGroupKey(string property)
{
var parameter = Expression.Parameter(typeof(Menu));
Expression body = null;
foreach(var propertyName in property.Split('.'))
{
Expression instance = body;
if(body == null)
instance = parameter;
body = Expression.Property(instance, propertyName);
}
return Expression.Lambda<Func<Menu, string>>(body, parameter);
}
This answer extends the GetGroupKey method I showed you in my answer to your previous question.