Normal
0
false
false
false
EN-GB
X-NONE
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:”Table Normal”;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:””;
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin-top:0cm;
mso-para-margin-right:0cm;
mso-para-margin-bottom:10.0pt;
mso-para-margin-left:0cm;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:”Calibri”,”sans-serif”;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;}
[Source: http://geekswithblogs.net/EltonStoneman]
Supposing you’re using a lambda expression as a neat way to access a named property:
s => s.FirstName
– when what you really want is to access it without using a string for the property name (“FirstName”), for example to specify the source or target property for a mapping exercise.
It’s easy if the expression is a direct property of the parameter type – a member of the “s” in the expression; casting the whole expression as a MemberExpression gives you access to the member as a PropertyInfo. You can encapsulate it in an extension method of LambdaExpression:
public static PropertyInfo AsPropertyInfo(this LambdaExpression expression)
{
PropertyInfo info = null;
if (expression.Body.NodeType == ExpressionType.MemberAccess)
{
info = ((MemberExpression)expression.Body).Member as PropertyInfo;
}
return info;
}
– which lets you access the property like this:
Expression<Func<FullUser, object>> exp = s => s.FirstName;
PropertyInfo sourceProperty = exp.AsPropertyInfo();
But if the expression represents a property of a nested type, or a property of a nested type of a nested type:
s => s.Address.Line1
s => s.Address.PostCode.Code
– then it’s different.
What you want in this case is an executable expression which will access the containing object of the property (e.g. get you the Address property of the source object, or the PostCode property of the Address property of the source object). You also want the PropertyInfo of the target property (e.g. Line1 or Code). And you want both the property accessor and the PropertyInfo from the same expression.
This is not too difficult. It doesn’t require a stack of target objects and PropertyInfos as originally feared. Firstly store the PropertyInfo from the nested target in the normal way:
targetPropertyInfo = targetExpression.AsPropertyInfo();
– this gives us access to Line1 or Code. Then store the expression which retrieves the path to the containing object for the property from the original source – .Address or .Address.Postcode. In the expression tree, this is the expression which contains the root to the original parameter:
if (targetExpression.Body.NodeType == ExpressionType.MemberAccess)
{
MemberExpression memberExpression = targetExpression.Body as MemberExpression;
if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
{
ParameterExpression parameter = GetParameterExpression(memberExpression.Expression);
if (parameter != null)
{
_targetExpression = Expression.Lambda(memberExpression.Expression, parameter);
}
}
}
The key line is the one which contains Expression.Lambda(). The parent expression is a MemberExpression (e.g. s.Address, or s.Address.PostCode) which itself is not executable. You can’t compile a MemberExpression and execute it on an object to get the member, as the expression needs to know the parameter it executes against.
Expression.Lambda creates a compilable, executable expression against a provided target parameter – which we already have from the “s” of the original expression, and we can find for any level of nesting:
private ParameterExpression GetParameterExpression(Expression expression)
{
while (expression.NodeType == ExpressionType.MemberAccess)
{
expression = ((MemberExpression)expression).Expression;
}
if (expression.NodeType == ExpressionType.Parameter)
{
return (ParameterExpression)expression;
}
return null;
}
So now we have the PropertyInfo for the nested target, and an expression which will give us the nested target, we can set the target property value like this:
object target; //this is the top-level object
//…
realTarget = expression.Compile().DynamicInvoke(target);
targetPropertyInfo.SetValue(realTarget, value, null);
It’s quite a complex requirement, but the solution is straightforward and the complexity is isolated from the calling code. Note that the example code above only deals with nested properties, not expressions with mixed properties and method calls (e.g. s.Address.GetPostCode().InwardCode), but it’s straightforward to cover those cases too.