In .NET both Action and Func classes are actually delegates that can be invoked to execute code, whereas the Expression class represents an expression tree that can potentially be compiled into executable code at run time. Expressions are particularly fun because they are malleable, and you can use them dynamically create delegates.
For example, you can create a generic statement that allows you to assign values to properties. This means that one piece of code can use a lambda to select a property for assignment, and another unrelated piece of code can use that expression to dynamically assign the value.
Thanks to AnxiousdeV for coming up with this solution on Stack Overflow.
Test
public class ExpressionTests
{
public int X { get; set; }
[Fact]
public void GetSetPropertyAction()
{
// Create an expression.
var expression = GetExpression<ExpressionTests, int?>(c => c.X);
// Use our extension method to create the action.
var assignAction = expression.GetSetPropertyAction();
// Set the property.
assignAction(this, 2);
// Assert that the value was set correctly.
Assert.Equal(2, X);
}
private static Expression<Func<T, U>> GetExpression<T, U>(
Expression<Func<T, U>> expression)
{
// We are only using this method to create an expression.
return expression;
}
}
Extension Method
public static class ExpressionExtensions
{
public static Action<T, U> GetSetPropertyAction<T, U>(
this Expression<Func<T, U>> expression)
{
// There will be a unary expression if the expression is
// implicitly casting one type to another, in this case we
// are casting a nullable int to an int.
var unaryExpression = expression.Body as UnaryExpression;
// unaryExpression = {Convert(c.X)}
// If there was no unary expression then we need to just get
// the body of the expression itself. If there was a unary
// expression we want to strip off the convert, as we will be
// setting the property value instead of getting it.
var targetExpression = unaryExpression != null
? unaryExpression.Operand
: expression.Body;
// targetExpression = {c.X}
// Get an expression representing the input parameter.
var typeParameterExpression = Expression.Parameter(typeof(U));
// typeParameterExpression = {Param_0}
// Convert the input type to the target type.
var convertExpression = Expression.Convert(
typeParameterExpression,
targetExpression.Type);
// convertExpression = {Convert(Param_0)}
// Create the expression that represents the actual
// assignment operation, this includes converting the value.
var assignExpression = Expression.Assign(
targetExpression,
convertExpression);
// assignExpression = {(c.X = Convert(Param_0))}
// Get an expression representing the target parameter.
var parameterExpression = expression.Parameters.Single();
// parameterExpression = {c}
// Combine the two inputs, the target and the value, into the a
// single assignment statement.
var labmdaExpression = Expression.Lambda<Action<T, U>>(
assignExpression,
parameterExpression,
typeParameterExpression);
// labmdaExpression = {(c, Param_0) => (c.X = Convert(Param_0))}
// Finally compile the new expession that we created into an
// action that we can actually use to set a value to the property.
return labmdaExpression.Compile();
}
}
Enjoy,
Tom
No comments:
Post a Comment