Lambda表达式树解析

16

我正在尝试在一个项目中使用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;它描述了您希望检索的数据。它不会被执行,而是被解释。 - user1228
这意味着该值在运行时不会替换为您的表达式,而是在编译时永久设置。为了获取该值,解释lambda的代码必须理解用户控件的概念以及如何提取它。 - user1228
除非解释表达式的代码可以访问您的控制并编写以执行此操作,否则不可能实现。如果代码具有过载或执行相同操作但接受实际值的其他方法,则可以使用该方法和lambda在运行时检索该值。 - user1228
4个回答

21
请记住,当您将lambda表达式视为表达式树时,您没有可执行的代码。相反,您拥有组成您编写的表达式的表达式元素树。
Charlie Calvert在一篇很好的文章中详细讨论了这一点。其中包括使用表达式可视化程序调试表达式的示例。
在您的情况下,要获取等式表达式右侧的值,您需要创建一个新的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; }
}

1

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

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


嗨约翰 - 或许这个问题更清晰:https://dev59.com/OHVC5IYBdhLWcg3wnCf6 - Keith Fitzgerald

0

刚刚在处理完全相同的问题,谢谢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<Func<object>>(Expression.Convert(expression, typeof(object))).Compile() - Double Down

-1

我不确定我理解了。你是在哪里“看到”这个的?是在设计时还是运行时?Lambda表达式本质上可以被视为匿名委托,并且将使用延迟执行。因此,你不应该期望在执行通过该行之后立即看到分配的值,显然是这样的。
但我认为这并不是你真正想问的...如果你能稍微澄清一下问题,也许我可以帮忙 :)


更新的问题。它在调试器中[当我尝试抓取表达式的右侧时也是如此]。 - Keith Fitzgerald
@Grank:Lambda表达式可以转换为表达式树或委托。听起来你可能在考虑转换为委托。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接