【问题标题】:Expression Tree parsing, variables ends up as constants表达式树解析,变量最终为常量
【发布时间】:2011-01-13 06:07:50
【问题描述】:

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

我一直在查看有关 StackOverflow 关于表达式树的其他问题,但似乎找不到我的问题的答案,所以这里是。

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

user => user.Email == email

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

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

如果我们再举一个例子:

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

这里我使用的是 ENUM(任务状态)。

所以我的问题是,在树解析中,我似乎在这两种情况下都得到了一个 ConstantExpression,我真的需要能够将它们区分开来。这些只是示例,所以我要问的是一种区分这两种表达类型的通用方式,因此我可以在解析中以两种不同的方式处理。

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

User user = db.Search (u => u.Email == email);

我正在尝试使用给定的电子邮件地址查找用户。我将其解析为存储过程,但我猜这不是重点。

第二个例子:

IList tasks = db.Search(t => t.Status != TaskStatus.Done && t.Status != TaskStatus.Failed);

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

再次感谢

【问题讨论】:

    标签: c# .net lambda tree expression


    【解决方案1】:

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

    你所拥有的是一个潜在的开放闭包 - 即值可以在表达式的执行之间发生变化,但在执行期间不会发生变化。所以它是一个“常数”。这是函数式编程和非函数式编程世界之间的范式差异:) 编程。

    考虑

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

    术语 a 是匿名类的成员访问,它封装并访问堆栈上的 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
    

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

    第二个例子可以正常工作 - 不需要额外的参数 consts 将是 consts。

    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");
    

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

    【讨论】:

    • 我会按照你的建议看一下父节点,看看有什么收获!谢谢。我会在这里发布我的发现!
    • 等一下 - 可能发生的事情是这样的......您是否定义了本地用户,并且它正试图将该实例作为 cloture 拉入。您可能想要做的是 Expression> x=>x.email == email;我想您可能对本地实例和所需输入感到困惑?
    • 在问题中添加了更多内容以澄清问题。我还是会看一下父节点,看看它会把我带到哪里。
    • 这看起来有点冗长。实际上,现在我已经通过查看“父”表达式来修复它,但我很想知道你对修复它有什么其他建议。这是我第一个使用表达式的项目,所以我还在学习!
    【解决方案2】:

    名字有点可惜,其实不是常数。

    它只是引用表达式之外的值。

    【讨论】:

      【解决方案3】:

      捕获的变量(第一种情况是email)通常是一个ConstantExpression,代表捕获类实例MemberExpressionFieldInfo 代表“变量” -好像你有:

      private class CaptureClass {
          public string email;
      }
      ...
      var obj = new CaptureClass();
      obj.email = "foo@bar.com";
      

      这里,obj 是表达式中的常量。

      所以:如果您看到 MemberExpression(字段的)到 ConstantExpression,它可能是一个捕获的变量。您还可以在捕获类中检查CompilerGeneratedAttribute...

      literal 常量通常只是ConstantExpression;事实上,很难想到使用常量成员的场景,除非你可以这样:

      () => "abc".Length
      

      但这里.Length 是一个属性(不是字段),并且该字符串可能没有[CompilerGenerated]

      【讨论】:

      • 我的问题可能不够清楚,但电子邮件实际上是用户对象的一个​​属性。
      • @SteenT - 表达式树是什么样的?它可能是一样的,默默地捕获this(因为email隐含地this.email)。所以我可能期待Member("email", Member("this", captureClass))(如果你明白我的意思)
      • @SteenT - 啊,在我的回答中,我说的是右侧的email。上面的评论应该解释左边的user.Email...
      【解决方案4】:

      只需检查 ConstantExpression 的类型。任何“常量”ConstantExpression 都有一个原始类型。

      【讨论】:

        猜你喜欢
        • 2015-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多