是否可以从更小的表达式组成 System.Linq.Expressions.Expression?
Is it possible to compose System.Linq.Expressions.Expression from smaller expressions?
我有一个简单的表达式,可用于将域对象转换为 DTO。
public static Expression<Func<Person, PersonDetailsShallow>> ToPersonDetailsShallow
=> (person) => new PersonDetailsShallow()
{
PersonId = person.Id,
Tag = person.Tag
};
public class Person
{
public string Id { get; internal set; }
public string Tag { get; internal set; }
}
public class PersonDetailsShallow
{
public string PersonId { get; internal set; }
public string Tag { get; internal set; }
}
我现在梦想有一种方法可以将这个 Expression
嵌入到另一个表达式中,比如
// define one more entity and dto to have something to work with
public class Purchase
{
public double Price { get; internal set; }
public Person Purchaser { get; internal set; }
}
public class PurchaseDetailsShallow
{
public double Price { get; internal set; }
public PersonDetailsShallow Purchaser { get; internal set; }
}
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ExpandExpression(ToPersonDetailsShallow, purchase.Purchaser)
}
其中 ExpandExpression
发挥了一些作用,结果 ToPurchaseDetailsShallow
看起来像这样
(purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = new PersonDetailsShallow()
{
PersonId = purchase.Purchaser.Id,
Tag = purchase.Purchaser.Tag
}
}
似乎有库可以实现这个问题
但我希望有一种不涉及添加新依赖项的更简单的方法。
我知道我可以使用 Compile()
a la
伪造它
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Compile()(purchase.Purchaser)
}
然而,它并没有创建正确的表达式树,而只是在计算表达式时表现出类似的行为。
要为 Expression
树实现 lambda 扩展器(还有其他类型的扩展器可以完成),您需要通过使用 XInvoke
方法调用它们来标记要扩展的 lambdas ,创建一个 ExpressionVisitor
来查找调用并展开它们,并使用常见的 Expression
替换访问者将 XInvoke
的参数应用于您正在展开的 lambda。
public static class ExpandXInvokeExt {
public static TRes XInvoke<TArg1, TRes>(this Expression<Func<TArg1, TRes>> fnE, TArg1 arg1)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1})");
public static TRes XInvoke<TArg1, TArg2, TRes>(this Expression<Func<TArg1, TArg2, TRes>> fnE, TArg1 arg1, TArg2 arg2)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1},{arg2})");
public static T ExpandXInvoke<T>(this T orig) where T : Expression => (T)new ExpandXInvokeVisitor().Visit(orig);
public static T Evaluate<T>(this T e) where T : Expression => (T)((e is ConstantExpression c) ? c.Value : Expression.Lambda(e).Compile().DynamicInvoke());
/// <summary>
/// ExpressionVisitor to expand a MethodCallExpression of XInvoke with an applied version of the first argument,
/// an Expression.
/// </summary>
public class ExpandXInvokeVisitor : ExpressionVisitor {
public override Expression Visit(Expression node) {
if (node?.NodeType == ExpressionType.Call) {
var callnode = (MethodCallExpression)node;
if (callnode.Method.Name == "XInvoke" && callnode.Method.DeclaringType == typeof(ExpandXInvokeExt)) {
var lambda = (LambdaExpression)(callnode.Arguments[0].Evaluate());
Expression expr = lambda.Body;
for (int argNum = 1; argNum < callnode.Arguments.Count; ++argNum)
expr = expr.Replace(lambda.Parameters[argNum - 1], callnode.Arguments[argNum]);
return expr;
}
}
return base.Visit(node);
}
}
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static T Replace<T>(this T orig, Expression from, Expression to) where T : Expression => (T)new ReplaceVisitor(from, to).Visit(orig);
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
}
这段代码提供了XInvoke
一个和两个参数,其他的可以用同样的方式添加。
有了这些可用的扩展,您可以这样写 ToPurchaseDetailsShallow
:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallowTemplate
=> (purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Invoke(purchase.Purchaser)
};
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow => ToPurchaseDetailsShallowTemplate.ExpandXInvoke();
注意:我使用名称 XInvoke
这样编译器就不会将错误数量的参数与调用 Expression.Invoke
的尝试混淆(我认为不应该,但确实如此)。
注意:如果有足够的括号和转换,您可以避免使用模板变量,但我不确定它是否有更好的效果:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> ((Expression<Func<Purchase, PurchaseDetailsShallow>>)(
(purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.XInvoke(purchase.Purchaser)
})
)
.ExpandXInvoke();
我有一个简单的表达式,可用于将域对象转换为 DTO。
public static Expression<Func<Person, PersonDetailsShallow>> ToPersonDetailsShallow
=> (person) => new PersonDetailsShallow()
{
PersonId = person.Id,
Tag = person.Tag
};
public class Person
{
public string Id { get; internal set; }
public string Tag { get; internal set; }
}
public class PersonDetailsShallow
{
public string PersonId { get; internal set; }
public string Tag { get; internal set; }
}
我现在梦想有一种方法可以将这个 Expression
嵌入到另一个表达式中,比如
// define one more entity and dto to have something to work with
public class Purchase
{
public double Price { get; internal set; }
public Person Purchaser { get; internal set; }
}
public class PurchaseDetailsShallow
{
public double Price { get; internal set; }
public PersonDetailsShallow Purchaser { get; internal set; }
}
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ExpandExpression(ToPersonDetailsShallow, purchase.Purchaser)
}
其中 ExpandExpression
发挥了一些作用,结果 ToPurchaseDetailsShallow
看起来像这样
(purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = new PersonDetailsShallow()
{
PersonId = purchase.Purchaser.Id,
Tag = purchase.Purchaser.Tag
}
}
似乎有库可以实现这个问题
但我希望有一种不涉及添加新依赖项的更简单的方法。
我知道我可以使用 Compile()
a la
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Compile()(purchase.Purchaser)
}
然而,它并没有创建正确的表达式树,而只是在计算表达式时表现出类似的行为。
要为 Expression
树实现 lambda 扩展器(还有其他类型的扩展器可以完成),您需要通过使用 XInvoke
方法调用它们来标记要扩展的 lambdas ,创建一个 ExpressionVisitor
来查找调用并展开它们,并使用常见的 Expression
替换访问者将 XInvoke
的参数应用于您正在展开的 lambda。
public static class ExpandXInvokeExt {
public static TRes XInvoke<TArg1, TRes>(this Expression<Func<TArg1, TRes>> fnE, TArg1 arg1)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1})");
public static TRes XInvoke<TArg1, TArg2, TRes>(this Expression<Func<TArg1, TArg2, TRes>> fnE, TArg1 arg1, TArg2 arg2)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1},{arg2})");
public static T ExpandXInvoke<T>(this T orig) where T : Expression => (T)new ExpandXInvokeVisitor().Visit(orig);
public static T Evaluate<T>(this T e) where T : Expression => (T)((e is ConstantExpression c) ? c.Value : Expression.Lambda(e).Compile().DynamicInvoke());
/// <summary>
/// ExpressionVisitor to expand a MethodCallExpression of XInvoke with an applied version of the first argument,
/// an Expression.
/// </summary>
public class ExpandXInvokeVisitor : ExpressionVisitor {
public override Expression Visit(Expression node) {
if (node?.NodeType == ExpressionType.Call) {
var callnode = (MethodCallExpression)node;
if (callnode.Method.Name == "XInvoke" && callnode.Method.DeclaringType == typeof(ExpandXInvokeExt)) {
var lambda = (LambdaExpression)(callnode.Arguments[0].Evaluate());
Expression expr = lambda.Body;
for (int argNum = 1; argNum < callnode.Arguments.Count; ++argNum)
expr = expr.Replace(lambda.Parameters[argNum - 1], callnode.Arguments[argNum]);
return expr;
}
}
return base.Visit(node);
}
}
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static T Replace<T>(this T orig, Expression from, Expression to) where T : Expression => (T)new ReplaceVisitor(from, to).Visit(orig);
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
}
这段代码提供了XInvoke
一个和两个参数,其他的可以用同样的方式添加。
有了这些可用的扩展,您可以这样写 ToPurchaseDetailsShallow
:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallowTemplate
=> (purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Invoke(purchase.Purchaser)
};
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow => ToPurchaseDetailsShallowTemplate.ExpandXInvoke();
注意:我使用名称 XInvoke
这样编译器就不会将错误数量的参数与调用 Expression.Invoke
的尝试混淆(我认为不应该,但确实如此)。
注意:如果有足够的括号和转换,您可以避免使用模板变量,但我不确定它是否有更好的效果:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> ((Expression<Func<Purchase, PurchaseDetailsShallow>>)(
(purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.XInvoke(purchase.Purchaser)
})
)
.ExpandXInvoke();