在C#编程中,表达式树是一种强大的功能,它允许开发者在编译时构建动态查询和其他复杂的代码结构。然而,表达式树的创建并不总是直观的,尤其是当涉及到属性或字段赋值时。编译器不会自动生成包含这些赋值的表达式树。最近,有人联系,希望开发一个使用Lambda表达式生成属性赋值表达式树的命令模式框架。
首先,定义一个简单的实体类:
public class Person
{
public string Name { get; set; }
}
目标是能够编写如下代码:
var et = Set((Person p) => p.Name = "me");
其中et
是代表属性赋值的表达式树。但是,由于编译器的限制,不能直接这样做。因此,尝试将获取属性信息和赋值操作分开:
var et = Set((Person p) => p.Name, () => "me");
这样,编译器就可以处理了。
下面是Set
方法的实现,它接收一个用于检索属性信息的表达式和一个用于检索要赋值的值的表达式:
public static Expression> Set(
Expression> propertyGetExpression,
Expression> valueExpression)
{
var entityParameterExpression = (ParameterExpression)(((MemberExpression)(propertyGetExpression.Body)).Expression);
return Expression.Lambda>(
Expression.Assign(propertyGetExpression.Body, valueExpression.Body),
entityParameterExpression);
}
这个方法从属性获取表达式的主体中获取属性信息,并使用值表达式的主体构建赋值表达式,然后使用与属性获取表达式相同的参数构建Lambda表达式。
现在,可以使用这个表达式将其转换为另一个上下文,或者编译并使用它:
var et = Set((Person p) => p.Name, () => name);
Console.WriteLine(person.Name); // Prints: p => (p.Name = "me")
var d = et.Compile();
d(person);
Console.WriteLine(person.Name); // Prints: me
它甚至可以支持闭包:
var et = Set((Person p) => p.Name, () => name);
Console.WriteLine(person.Name); // Prints: p => (p.Name = value(<>c__DisplayClass0).name)
var d = et.Compile();
name = "me";
d(person);
Console.WriteLine(person.Name); // Prints: me
name = "you";
d(person);
Console.WriteLine(person.Name); // Prints: you
虽然在预期的场景中不太有用,但还可以构建一个接受要赋值的属性值作为参数的表达式树:
public static Expression> Set(Expression> propertyGetExpression)
{
var entityParameterExpression = (ParameterExpression)(((MemberExpression)(propertyGetExpression.Body)).Expression);
var valueParameterExpression = Expression.Parameter(typeof(TValue));
return Expression.Lambda>(
Expression.Assign(propertyGetExpression.Body, valueParameterExpression),
entityParameterExpression,
valueParameterExpression);
}
这个新的表达式可以像这样使用:
var et = Set((Person p) => p.Name);
Console.WriteLine(person.Name); // Prints: (p, Param_0) => (p.Name = Param_0)
var d = et.Compile();
d(person, "me");
Console.WriteLine(person.Name); // Prints: me
d(person, "you");
Console.WriteLine(person.Name); // Prints: you