Lambda 表达式树解析

Posted

技术标签:

【中文标题】Lambda 表达式树解析【英文标题】:Lambda Expression Tree Parsing 【发布时间】:2010-09-19 07:11:54 【问题描述】:

我正在尝试在项目中使用 Lambda 表达式来映射到第三方查询 API。所以,我正在手动解析表达式树。

如果我传入如下 lambda 表达式:

p => p.Title == "title"

一切正常。

但是,如果我的 lambda 表达式看起来像:

p => p.Title == myaspdropdown.SelectedValue

使用 .NET 调试器,我看不到该功能的实际价值。相反,我看到的是:

p => p.Title = (value(ASP.usercontrols_myaspusercontrol_ascx).myaspdropdown.SelectedValue)

什么给了?当我尝试将表达式的右侧作为字符串抓取时,我得到 (value(ASP.usercontrols_myaspusercontrol_ascx).myaspdropdown.SelectedValue) 而不是实际值。 如何获得实际值?

【问题讨论】:

我认为问题是(而且我不是表达式专家,所以这不是答案)是表达式不是 lambda;它描述了您希望检索的数据。它没有被执行,它被解释了。 这意味着该值不会在运行时替换您的表达式,而是在编译时永久设置。为了获取值,解释 lambda 的代码必须了解用户控件的概念以及如何提取它 除非解释表达式的代码可以访问您的控件并且被编码为执行此操作,否则这是不可能的。如果代码有一个重载或另一个执行相同操作但获取实际值的方法,您可以使用它和一个 lambda 在运行时检索该值。 【参考方案1】:

请记住,当您将 lambda 表达式作为表达式树处理时,您没有可执行代码。相反,您有一个表达式元素树,构成您编写的表达式。

Charlie Calvert 的a good post 对此进行了详细讨论。包括使用表达式可视化工具调试表达式的示例。

在您的情况下,要获取等式表达式右侧的值,您需要创建一个新的 lambda 表达式,编译它然后调用它。

我已经编写了一个简单的示例 - 希望它能够满足您的需求。

public class Class1

    public string Selection  get; set; 

    public void Sample()
    
        Selection = "Example";
        Example<Book, bool>(p => p.Title == Selection);
    

    public void Example<T,TResult>(Expression<Func<T,TResult>> exp)
    
        BinaryExpression equality = (BinaryExpression)exp.Body;
        Debug.Assert(equality.NodeType == ExpressionType.Equal);

        // Note that you need to know the type of the rhs of the equality
        var accessorExpression = Expression.Lambda<Func<string>>(equality.Right);
        Func<string> accessor = accessorExpression.Compile();
        var value = accessor();
        Debug.Assert(value == Selection);
    


public class Book

    public string Title  get; set; 

【讨论】:

【参考方案2】:

要获得实际值,您需要将表达式树的逻辑应用于您拥有的任何上下文。

表达式树的全部意义在于它们将逻辑表示为数据,而不是评估表达式。您需要弄清楚 lambda 表达式的真正含义。这可能意味着根据本地数据评估其中的某些部分 - 您需要自己决定。表达式树非常强大,但是解析和使用它们并不是一件简单的事情。 (问问任何写过 LINQ 提供程序的人...... Frans Bouma 曾多次抱怨过这些困难。)

【讨论】:

嗨,约翰-也许这个问题更清楚:***.com/questions/238765/…【参考方案3】:

一直在努力解决完全相同的问题,谢谢 Bevan。在扩展上,以下是可用于提取值的通用模式(在我的查询引擎中使用)。

    [TestFixture]
public class TestClass

    [Test]
    public void TEst()
    
        var user = new User Id = 123;
        var idToSearch = user.Id;
        var query = Creator.CreateQuery<User>()
            .Where(x => x.Id == idToSearch);
    


public class Query<T>

    public Query<T> Where(Expression<Func<T, object>> filter)
    
        var rightValue = GenericHelper.GetVariableValue(((BinaryExpression)((UnaryExpression)filter.Body).Operand).Right.Type, ((BinaryExpression)((UnaryExpression)filter.Body).Operand).Right);
        Console.WriteLine(rightValue);
        return this;
    


internal class GenericHelper

    internal static object GetVariableValue(Type variableType, Expression expression)
    
        var targetMethodInfo = typeof(InvokeGeneric).GetMethod("GetVariableValue");
        var genericTargetCall = targetMethodInfo.MakeGenericMethod(variableType);
        return genericTargetCall.Invoke(new InvokeGeneric(), new[]  expression );
    


internal class InvokeGeneric

    public T GetVariableValue<T>(Expression expression) where T : class
    
        var accessorExpression = Expression.Lambda<Func<T>>(expression);
        var accessor = accessorExpression.Compile();
        return accessor();
    

【讨论】:

与其使用你的反射调用 InvokeGeneric,我会做的是Expression&lt;Func&lt;object&gt;&gt;(Expression.Convert(expression, typeof(object))).Compile()【参考方案4】:

我不确定我是否理解。你在哪里“看到”那个?那是在设计时还是运行时? Lambda 表达式本质上可以被认为是匿名委托,并且将在延迟执行的情况下进行操作。因此,显然,在执行通过该行之前,您不应该期望看到分配的值。 我不认为这真的是你的意思......如果你澄清一下这个问题也许我可以提供帮助:)

【讨论】:

更新问题。它在调试器中[以及当我尝试抓住表达式的右侧时] @Grank:Lambda 表达式可以转换为表达式树或委托。听起来您正在考虑转换为委托。

以上是关于Lambda 表达式树解析的主要内容,如果未能解决你的问题,请参考以下文章

C#进阶 | 全面解析Lambda表达式

C# 表达式树的重要意义

C# 表达式树 创建生成使用lambda转成表达式树~表达式树的知识详解

Lambda表达式和Lambda表达式树

表达式树 Expression Trees

Lambda表达式树构建(上)