表达式树解析,变量最终成为常量

时间:2011-01-13 06:07:51

标签: c# .net lambda tree expression

所以我正在开发一个需要解析表达式树的项目。我得到了大部分工作,但我遇到了一些问题。

我一直在关注表达树上的StackOverflow上的其他问题,但似乎无法找到我的问题的答案,所以这里有。

我的问题是常量和变量之间的差异(或缺乏)。让我从一个例子开始:

user => user.Email == email

这显然不是常量而是变量,但这最终成为表达式树中某处的ConstantExpression。如果你看一下表达式本身,看起来有点奇怪:

Expression = {value(stORM.Web.Security.User+<>c__DisplayClassa)}

如果我们采取另一个例子:

task => task.Status != TaskStatus.Done && t.Status != TaskStatus.Failed

这里我使用的是ENUM(TaskStatus)。

所以我的问题是,在树解析中,我似乎在两种情况下最终都使用了ConstantExpression,而且我真的需要能够区分它们。这些只是示例,所以我要问的是一种通用的方式来说明这两种类型的表达式,所以我可以在解析时以两种不同的方式处理。

编辑:好的,我的例子可能不太清楚,所以我会再试一次。第一个例子:

用户user = db.Search&lt;用户&gt; (u =&gt; u.Email == email);

我正在尝试找到具有给定电子邮件地址的用户。我正在将其解析为存储过程,但除此之外,我猜是这样。

第二个例子:

IList&lt;任务&gt; tasks = db.Search(t =&gt; t.Status!= TaskStatus.Done&amp;&amp; t.Status!= TaskStatus.Failed);

在这里,我试图找到状态与“完成”和“失败”不同的所有任务。 这又是解析为存储过程。在第一个示例中,我的代码需要确定存储过程需要输入参数,即email变量的值。在第二个例子中,我不需要任何输入参数,我只需要创建用于选择状态不同于Done和Failed的任务的SQL。

再次感谢

4 个答案:

答案 0 :(得分:3)

因此,从表达的角度来看,价值是一个常数。它不能被表达式改变。

你所拥有的是一个可能打开的闭包 - 即值可以在表达式的执行之间改变,但不能在它期间改变。所以这是一个“常数”。这是函数式编程和非函数式编程之间的范式差异。

考虑

        int a =2;
        Expression<Func<int, int>> h = x=> x+ a;
        Expression<Func<int, int>> j = x => x +2;
        a = 1;

术语a是对匿名类的成员访问,该匿名类包装并访问堆栈上的变量。第一个节点是一个MemberAccess节点,然后在其下面 - 表达式是一个常量。

对于上面的代码:

((SimpleBinaryExpression)(h.Body)).Right
{value(WindowsFormsApplication6.Form1+<>c__DisplayClass0).a}
    CanReduce: false
    DebugView: ".Constant<WindowsFormsApplication6.Form1+<>c__DisplayClass0>(WindowsFormsApplication6.Form1+<>c__DisplayClass0).a"
    Expression: {value(WindowsFormsApplication6.Form1+<>c__DisplayClass0)}
    Member: {Int32 a}
    NodeType: MemberAccess
    Type: {Name = "Int32" FullName = "System.Int32"}

下面的常数:

((MemberExpression)((SimpleBinaryExpression)(h.Body)).Right).Expression
{value(WindowsFormsApplication6.Form1+<>c__DisplayClass0)}
    CanReduce: false
    DebugView: ".Constant<WindowsFormsApplication6.Form1+<>c__DisplayClass0>(WindowsFormsApplication6.Form1+<>c__DisplayClass0)"
    NodeType: Constant
    Type: {Name = "<>c__DisplayClass0" FullName = "WindowsFormsApplication6.Form1+<>c__DisplayClass0"}
    Value: {WindowsFormsApplication6.Form1.}
        }
    }

普通老人2出现在:

((SimpleBinaryExpression)(j.Body)).Right
{2}
    CanReduce: false
    DebugView: "2"
    NodeType: Constant
    Type: {Name = "Int32" FullName = "System.Int32"}
    Value: 2

所以我不知道这对你有帮助。您可以通过查看父节点 - 或父节点正在访问的对象类型来判断。


根据您的澄清添加 -

所以当你说

user => user.Email == email

您的意思是查找所有用户的电子邮件等于传入的参数 - 但该链接表达式意味着完全不同的东西。

你想说的是

Expression<Func<User, string, bool>> (user, email) => user.Email == email

这样,电子邮件现在将成为参数。如果你不喜欢,你可以做另外一件事。

第二个例子可以正常工作 - 不需要额外的参数,因为它们将是最佳的。

t => t.Status != TaskStatus.Done && t.Status != TaskStatus.Failed

编辑:添加另一种方式:

因此,为了让代码正常工作,您需要做的一件事就是在lambda之外声明一个字符串电子邮件 - 这有点笨拙。

您可以通过将参数放在特定位置(如静态类)来更好地识别参数。然后当你通过Lambda时,你不必看一些可怕的cloture对象 - 而是一个很好的静态类。

public static class Parameter
{
    public static T Input<T>(string name)
    {
        return default(T);
    }  
}

然后您的代码如下所示:

Expression<Func<User, bool>> exp = x => x.Email == Parameter.Input<String>("email");

然后您可以遍历树 - 当您调用参数静态类时,您可以查看类型和名称(在参数集合中),然后关闭....

答案 1 :(得分:2)

这个名字有点不幸,实际上并不是一个常数。

它只是指代表达式之外的值。

答案 2 :(得分:2)

捕获的变量(email的第一个案例)通常是表示捕获类实例ConstantExpressionMemberExpression到{{1}对于“变量” - 就好像你有:

FieldInfo

这里,private class CaptureClass { public string email; } ... var obj = new CaptureClass(); obj.email = "foo@bar.com"; 是表达式中的常量。

所以:如果你看到一个obj(一个字段)到MemberExpression,那么可能是一个捕获的变量。您还可以在捕获类...

上检查ConstantExpression

常量通常CompilerGeneratedAttribute;实际上,很难想象你使用常量成员的情况,除非你能够这样:

ConstantExpression

但此处() => "abc".Length 属性(不是字段),字符串可能没有.Length

答案 3 :(得分:0)

只需检查ConstantExpression的类型即可。任何“常量”ConstantExpression都有一个基本类型。