Introduction
In one of the projects I was working on, I needed to get property value from property path and property path from expression.
First, let's cover the second case.
With expression in the form of nested property value:
()=>object1.object2.object3.object4
we cannot take simple value of some property, because we have only root object, object1
.
Instead, we have to take every value of every object in the middle and last object as value of our desired property.
If we would have expression like this:
()=>object1.object2
and value of root object (object1
), we can just cast expression above to MemberExpression
type and then retrieve name of the property, from property MemberExpression.Member.Name
.
But if we need another property of object3
or even deeper, we need to retrieve another, nested MemberExpression
from the expression above.
Without knowledge of depth of our expression, we have to repeat that operation as long as property MemberExpression
.Expression has value different than null
.
To take MemberExpression
from given Expression
(which can be of many types: MemberExpression
, LambdaExpression
, UnaryExpression
), we can use the following method:
public static MemberExpression GetMemberExpression(Expression expression)
{
if (expression is MemberExpression)
{
return (MemberExpression)expression;
}
else if (expression is LambdaExpression)
{
var lambdaExpression = expression as LambdaExpression;
if (lambdaExpression.Body is MemberExpression)
{
return (MemberExpression)lambdaExpression.Body;
}
else if (lambdaExpression.Body is UnaryExpression)
{
return ((MemberExpression)((UnaryExpression)lambdaExpression.Body).Operand);
}
}
return null;
}
This method will return MemberExpression
from any of the above types.
Armed with method like this, we can write loop to retrieve property name for all levels of expression. For example, we can use rarely used do...while
loop. Of course, we can use while
loop, but this way we can have additional learning experience, using less known language constructs. :)
public static string GetPropertyPath(Expression expr)
{
var path = new StringBuilder();
MemberExpression memberExpression = GetMemberExpression(expr);
do
{
if (path.Length > 0)
{
path.Insert(0, ".");
}
path.Insert(0, memberExpression.Member.Name);
memberExpression = GetMemberExpression(memberExpression.Expression);
}
while (memberExpression != null);
return path.ToString();
}
In my code, I placed those two methods in one class called ExpressionOperator
and then used them in extension for type Object
:
public static string GetPropertyPath<TObj, TRet>(this TObj obj,
Expression<Func<TObj, TRet>> expr)
{
return ExpressionOperator.GetPropertyPath(expr);
}
which can be used like this:
object1.GetPropertyPath(o =>o.object2.object3.object4)
which should return "object2.object3.object4
" string
. With possibility for returning a property path from any expression, we can now write method that returns value of destination property (last in the expression).
Method like this is even simpler than for returning property path. We just need to find value of every property in the middle of path to the point, when we have our destination property. For that, we can use while
loop. :)
public static object GetPropertyValue(this object obj, string propertyPath)
{
object propertyValue = null;
if (propertyPath.IndexOf(".") < 0)
{
var objType = obj.GetType();
propertyValue = objType.GetProperty(propertyPath).GetValue(obj, null);
return propertyValue;
}
var properties = propertyPath.Split('.').ToList();
var midPropertyValue = obj;
while (properties.Count > 0)
{
var propertyName = properties.First();
properties.Remove(propertyName);
propertyValue = midPropertyValue.GetPropertyValue(propertyName);
midPropertyValue = propertyValue;
}
return propertyValue;
}
The above code returns value of property by reflection. Property name is taken by splitting property path into parts separated by '.'. For example, we can use this method in the following way:
object1.GetPropertyValue(o=>o.object2.object3.object4);
This is really simple example. For better usability, you should add validating for root object (if this object has value in the first place) and for any of mid-objects. They did not have to have value too, they could not have value because of lack of initialization of object tree. Also a good idea is to add boolean flag, if we want method to return an error in the above cases or false
(lack of success) and null
value (value of desired property).
Another way to improve things is to add similar way to retrieve type of nested property or set property value by property path given as string
.
In the project I was working on, I used this mechanism to retrieve properties path in C# code, transport it to client (it was a web application so client was a browser) and set property value in JavaScript object, which had the same object tree. In other way, I transported value of changed property at the client side and its path, to apply changes at server side. Very useful to synchronize two different data schemes.
Attached to this article (see download link at the top) is an example console application for retrieving property path and property value of a nested property.
I hope this will help. :)
CodeProject