从另外两个创建动态表达式 lambda(链接表达式)

Posted

技术标签:

【中文标题】从另外两个创建动态表达式 lambda(链接表达式)【英文标题】:Create dynamic Expression lambda from two others (chaining the Expressions) 【发布时间】:2011-12-13 23:29:53 【问题描述】:

给定一个接受一个标识对象并返回一个属性的 lambda:

Expression<Func<Identification, object>> fx = _ => _.Id;

还有一个将对象转换为标识实例的转换 lambda:

ParameterExpression p = Expression.Parameter(typeof(object), "o");
Expression @new = Expression.Lambda(Expression.Convert(p, typeof(Identification)), p);

如何构建一个新的 lambda,它执行 @new(取出标识实例)并将结果传递给 fx。我需要@new的结果以某种方式绑定到fx的第一个参数,但我找不到示例。

我需要结果是Expression,它应该是Expression&lt;Func&lt;object, object&gt;&gt; 类型,它应该将入站参数转换为Identification,然后获取Id 属性。

【问题讨论】:

【参考方案1】:

首先,请注意,如果您适当地键入@new,这会更容易,即:

LambdaExpression @new = ...

因为这样可以轻松访问@new.Body@new.Parameters;完成了, Expression.Invoke 在这里很有用:

var combinedParam = Expression.Parameter(typeof(object), "o");
var combined = Expression.Lambda<Func<object, object>>(
    Expression.Invoke(fx,
        Expression.Invoke(@new, combinedParam)), combinedParam);

虽然为了更简洁的表达式,你也可以使用ExpressionVisitor来完全替换内部表达式:

var injected = new SwapVisitor(fx.Parameters[0], @new.Body).Visit(fx.Body);
var combined = Expression.Lambda<Func<object, object>>(injected,@new.Parameters);

与:

class SwapVisitor : ExpressionVisitor 
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to) 
        this.from = from;
        this.to = to;
    
    public override Expression Visit(Expression node) 
        return node == from ? to : base.Visit(node);
    

这是做什么的:

检查fx.Body 树,将_(参数)的所有实例替换为@new.Body(请注意,这将包含对o 参数(又名p)的引用 然后我们从替换后的表达式构建一个新的 lambda,重新使用来自 @new 的相同参数,以确保我们注入的值将被正确绑定

【讨论】:

嗨,我实际上已经接近尝试了,但在 linqpad 中看起来完全错误。最终的表达式树看起来很可怕,即使简化了——但它完美地完成了工作,谢谢。如果您一次完成所有操作,编译器会生成一个更整洁的树。 (object p) => ((Identification)p).Id 你会得到 Convert(Convert(p).Id) 这真的很整洁。无论如何,谢谢你的回答。 @Jim - 你刷新了吗?第二个版本完全创建 o =&gt; Convert(Convert(o).Id),而不是 o =&gt; Invoke(_ =&gt; Convert(_.Id), Invoke(o =&gt; Convert(o), o)) 我已经使用了访问者,并生成了新的表达式。效果很好,谢谢。【参考方案2】:

使用来自Marc Gravell's answer 的代码,您可以通过辅助函数很好地简化此过程:

public static class ExpressionHelper 
    public static Expression<Func<TFrom, TTo>> Chain<TFrom, TMiddle, TTo>(
        this Expression<Func<TFrom, TMiddle>> first,
        Expression<Func<TMiddle, TTo>> second
    ) 
        return Expression.Lambda<Func<TFrom, TTo>>(
           new SwapVisitor(second.Parameters[0], first.Body).Visit(second.Body),
           first.Parameters
        );
    

    private class SwapVisitor : ExpressionVisitor 
        private readonly Expression _from;
        private readonly Expression _to;

        public SwapVisitor(Expression from, Expression to) 
            _from = from;
            _to = to;
        

        public override Expression Visit(Expression node) 
            return node == _from ? _to : base.Visit(node);
        
    

现在看看有多干净!

var valueSelector = new Expression<Func<MyTable, int>>(o => o.Value);
var intSelector = new Expression<Func<int, bool>>(x => x > 5);
var selector = valueSelector.Chain<MyTable, int, bool>(intSelector);

它适用于实体框架和其他需要干净的Expression 而不会尝试在其中调用Func 的东西。

【讨论】:

以上是关于从另外两个创建动态表达式 lambda(链接表达式)的主要内容,如果未能解决你的问题,请参考以下文章

从对象集合动态构建 lambda 表达式?

c# 动态创建带字符串拼接的lambda表达式?

哪位高人指点一下,C#中两个动态lambda 表达式有啥办法合并成一个? 谢谢!

Python:lambda表达式实现求两个变量的最大值

如果不首先将 lambda 表达式转换为委托或表达式树类型,则无法将 lambda 表达式用作动态分派操作的参数

使用动态唯一键值对linq / lambda表达式列表过滤数据