所以我正在开发一个需要解析表达式树的项目。我得到了大部分工作,但我遇到了一些问题。
我一直在关注表达树上的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。
再次感谢
答案 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
的第一个案例)通常是表示捕获类实例的ConstantExpression
,MemberExpression
到{{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都有一个基本类型。