如何将字符串转换为其等效的 LINQ 表达式树?
Posted
技术标签:
【中文标题】如何将字符串转换为其等效的 LINQ 表达式树?【英文标题】:How to convert a String to its equivalent LINQ Expression Tree? 【发布时间】:2010-10-23 17:02:49 【问题描述】:这是原始问题的简化版本。
我有一个名为 Person 的类:
public class Person
public string Name get; set;
public int Age get; set;
public int Weight get; set;
public DateTime FavouriteDay get; set;
...让我们说一个实例:
var bob = new Person
Name = "Bob",
Age = 30,
Weight = 213,
FavouriteDay = '1/1/2000'
我想在我最喜欢的文本编辑器中将以下内容写为 字符串....
(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3
我想获取这个字符串和我的对象实例并评估 TRUE 或 FALSE - 即评估对象实例上的 Func
这是我目前的想法:
-
在 ANTLR 中实现基本语法以支持基本的比较和逻辑运算符。我正在考虑在此处复制 Visual Basic 优先级和一些功能集:http://msdn.microsoft.com/en-us/library/fw84t893(VS.80).aspx
让 ANTLR 根据提供的字符串创建合适的 AST。
遍历 AST 并使用 Predicate Builder 框架动态创建 Func
我的问题是我完全把它烤过头了吗?有其他选择吗?
编辑:选择的解决方案
我决定使用动态 Linq 库,特别是 LINQSamples 中提供的动态查询类。
代码如下:
using System;
using System.Linq.Expressions;
using System.Linq.Dynamic;
namespace ExpressionParser
class Program
public class Person
public string Name get; set;
public int Age get; set;
public int Weight get; set;
public DateTime FavouriteDay get; set;
static void Main()
const string exp = @"(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3";
var p = Expression.Parameter(typeof(Person), "Person");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] p , null, exp);
var bob = new Person
Name = "Bob",
Age = 30,
Weight = 213,
FavouriteDay = new DateTime(2000,1,1)
;
var result = e.Compile().DynamicInvoke(bob);
Console.WriteLine(result);
Console.ReadKey();
结果是 System.Boolean 类型,在本例中为 TRUE。
非常感谢 Marc Gravell。
包括System.Linq.Dynamicnuget 包,文档here
【问题讨论】:
感谢您发布完整的解决方案代码以及您的问题。非常感谢。 如果你有一个收藏或人并且想要过滤一些元素怎么办? Person.Age > 3 AND Person.Weight > 50 ? 谢谢。我找不到 DynamicExpression.ParseLambda()。它在哪个命名空间和程序集中? 一切都好.. 命名空间之间存在歧义。需要 - 使用 E = System.Linq.Expressions;使用 System.Linq.Dynamic; 为什么它使用'AND'而不是'&&'。不应该是C#代码吗? 【参考方案1】:dynamic linq library 会在这里提供帮助吗?特别是,我认为是Where
子句。如有必要,将其放入列表/数组中,以便在其上调用.Where(string)
!即
var people = new List<Person> person ;
int match = people.Where(filter).Any();
如果没有,写一个解析器(使用 Expression
在引擎盖下)并不是很费力 - 我在圣诞节前的火车通勤中写了一个类似的(虽然我不认为我有源代码)......
【讨论】:
标记一下“写一个解析器(在底层使用表达式)”解析然后生成一个表达式树是什么意思,还是System.Linq.Expressions有一些解析机制? 我很确定他想读入具有这样形成的表达式的文件,然后将其翻译为谓词并编译。问题似乎是,让你的语法从“字符串”转换为“谓词”。// Lambda expression as data in the form of an expression tree.
System.Linq.Expressions.Expression<Func<int, bool>> expr = i => i < 5;
// Compile the expression tree into executable code.
Func<int, bool> deleg = expr.Compile();
// Invoke the method and print the output.
Console.WriteLine("deleg(4) = 0", deleg(4));
ParseLambda 好!
这是皇冠上的明珠。我用谷歌搜索和搜索并尝试了 6 种不同的开源解决方案,浪费了几个小时 - 如果没有 buts 和 ifs,没有一个可以工作,每个 dll 大小都比 LINQ 动态大几倍。我在大约 20 分钟内为我完成了 LINQ。【参考方案2】:
另一个这样的库是Flee
我对@987654322@ 和Flee 进行了快速比较,对于表达式"(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"
,Flee 的速度快了10 倍
这是使用 Flee 编写代码的方法。
static void Main(string[] args)
var context = new ExpressionContext();
const string exp = @"(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3";
context.Variables.DefineVariable("Person", typeof(Person));
var e = context.CompileDynamic(exp);
var bob = new Person
Name = "Bob",
Age = 30,
Weight = 213,
FavouriteDay = new DateTime(2000, 1, 1)
;
context.Variables["Person"] = bob;
var result = e.Evaluate();
Console.WriteLine(result);
Console.ReadKey();
【讨论】:
也许我遗漏了一些东西,但是 'flee' 如何帮助构建 linq 表达式树?【参考方案3】:void Main()
var testdata = new List<Ownr>
//new OwnrName = "abc", Qty = 20, // uncomment this to see it getting filtered out
new OwnrName = "abc", Qty = 2,
new OwnrName = "abcd", Qty = 11,
new OwnrName = "xyz", Qty = 40,
new OwnrName = "ok", Qty = 5,
;
Expression<Func<Ownr, bool>> func = Extentions.strToFunc<Ownr>("Qty", "<=", "10");
func = Extentions.strToFunc<Ownr>("Name", "==", "abc", func);
var result = testdata.Where(func.ExpressionToFunc()).ToList();
result.Dump();
public class Ownr
public string Name get; set;
public int Qty get; set;
public static class Extentions
public static Expression<Func<T, bool>> strToFunc<T>(string propName, string opr, string value, Expression<Func<T, bool>> expr = null)
Expression<Func<T, bool>> func = null;
try
var type = typeof(T);
var prop = type.GetProperty(propName);
ParameterExpression tpe = Expression.Parameter(typeof(T));
Expression left = Expression.Property(tpe, prop);
Expression right = Expression.Convert(ToExprConstant(prop, value), prop.PropertyType);
Expression<Func<T, bool>> innerExpr = Expression.Lambda<Func<T, bool>>(ApplyFilter(opr, left, right), tpe);
if (expr != null)
innerExpr = innerExpr.And(expr);
func = innerExpr;
catch (Exception ex)
ex.Dump();
return func;
private static Expression ToExprConstant(PropertyInfo prop, string value)
object val = null;
try
switch (prop.Name)
case "System.Guid":
val = Guid.NewGuid();
break;
default:
val = Convert.ChangeType(value, prop.PropertyType);
break;
catch (Exception ex)
ex.Dump();
return Expression.Constant(val);
private static BinaryExpression ApplyFilter(string opr, Expression left, Expression right)
BinaryExpression InnerLambda = null;
switch (opr)
case "==":
case "=":
InnerLambda = Expression.Equal(left, right);
break;
case "<":
InnerLambda = Expression.LessThan(left, right);
break;
case ">":
InnerLambda = Expression.GreaterThan(left, right);
break;
case ">=":
InnerLambda = Expression.GreaterThanOrEqual(left, right);
break;
case "<=":
InnerLambda = Expression.LessThanOrEqual(left, right);
break;
case "!=":
InnerLambda = Expression.NotEqual(left, right);
break;
case "&&":
InnerLambda = Expression.And(left, right);
break;
case "||":
InnerLambda = Expression.Or(left, right);
break;
return InnerLambda;
public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
public static Func<T, TResult> ExpressionToFunc<T, TResult>(this Expression<Func<T, TResult>> expr)
var res = expr.Compile();
return res;
LinqPad 有 Dump()
方法
【讨论】:
GetProperty 方法在哪里? @Alen.Toma 我必须将代码更改为var type = typeof(T); var prop = type.GetProperty(propName);
才能编译。
让它编译并转储输出【参考方案4】:
您可以查看DLR。它允许您在 .NET 2.0 应用程序中评估和执行脚本。这是IronRuby 的示例:
using System;
using IronRuby;
using IronRuby.Runtime;
using Microsoft.Scripting.Hosting;
class App
static void Main()
var setup = new ScriptRuntimeSetup();
setup.LanguageSetups.Add(
new LanguageSetup(
typeof(RubyContext).AssemblyQualifiedName,
"IronRuby",
new[] "IronRuby" ,
new[] ".rb"
)
);
var runtime = new ScriptRuntime(setup);
var engine = runtime.GetEngine("IronRuby");
var ec = Ruby.GetExecutionContext(runtime);
ec.DefineGlobalVariable("bob", new Person
Name = "Bob",
Age = 30,
Weight = 213,
FavouriteDay = "1/1/2000"
);
var eval = engine.Execute<bool>(
"return ($bob.Age > 3 && $bob.Weight > 50) || $bob.Age < 3"
);
Console.WriteLine(eval);
public class Person
public string Name get; set;
public int Age get; set;
public int Weight get; set;
public string FavouriteDay get; set;
当然,这种技术是基于运行时评估,无法在编译时验证代码。
【讨论】:
我希望能够防止执行“错误代码”...这是否合适? “错误代码”是什么意思?有人输入了无效的表达式?在这种情况下,您将在尝试评估脚本时遇到运行时异常。 @darin,诸如启动进程、更改数据等。 'bad code' = 不是 Func这是一个基于 Scala DSL 的解析器组合器的示例,用于解析和评估算术表达式。
import scala.util.parsing.combinator._
/**
* @author Nicolae Caralicea
* @version 1.0, 04/01/2013
*/
class Arithm extends JavaTokenParsers
def expr: Parser[List[String]] = term ~ rep(addTerm | minusTerm) ^^
case termValue ~ repValue => termValue ::: repValue.flatten
def addTerm: Parser[List[String]] = "+" ~ term ^^
case "+" ~ termValue => termValue ::: List("+")
def minusTerm: Parser[List[String]] = "-" ~ term ^^
case "-" ~ termValue => termValue ::: List("-")
def term: Parser[List[String]] = factor ~ rep(multiplyFactor | divideFactor) ^^
case factorValue1 ~ repfactor => factorValue1 ::: repfactor.flatten
def multiplyFactor: Parser[List[String]] = "*" ~ factor ^^
case "*" ~ factorValue => factorValue ::: List("*")
def divideFactor: Parser[List[String]] = "/" ~ factor ^^
case "/" ~ factorValue => factorValue ::: List("/")
def factor: Parser[List[String]] = floatingPointConstant | parantExpr
def floatingPointConstant: Parser[List[String]] = floatingPointNumber ^^
case value => List[String](value)
def parantExpr: Parser[List[String]] = "(" ~ expr ~ ")" ^^
case "(" ~ exprValue ~ ")" => exprValue
def evaluateExpr(expression: String): Double =
val parseRes = parseAll(expr, expression)
if (parseRes.successful) evaluatePostfix(parseRes.get)
else throw new RuntimeException(parseRes.toString())
private def evaluatePostfix(postfixExpressionList: List[String]): Double =
import scala.collection.immutable.Stack
def multiply(a: Double, b: Double) = a * b
def divide(a: Double, b: Double) = a / b
def add(a: Double, b: Double) = a + b
def subtract(a: Double, b: Double) = a - b
def executeOpOnStack(stack: Stack[Any], operation: (Double, Double) => Double): (Stack[Any], Double) =
val el1 = stack.top
val updatedStack1 = stack.pop
val el2 = updatedStack1.top
val updatedStack2 = updatedStack1.pop
val value = operation(el2.toString.toDouble, el1.toString.toDouble)
(updatedStack2.push(operation(el2.toString.toDouble, el1.toString.toDouble)), value)
val initial: (Stack[Any], Double) = (Stack(), null.asInstanceOf[Double])
val res = postfixExpressionList.foldLeft(initial)((computed, item) =>
item match
case "*" => executeOpOnStack(computed._1, multiply)
case "/" => executeOpOnStack(computed._1, divide)
case "+" => executeOpOnStack(computed._1, add)
case "-" => executeOpOnStack(computed._1, subtract)
case other => (computed._1.push(other), computed._2)
)
res._2
object TestArithmDSL
def main(args: Array[String]): Unit =
val arithm = new Arithm
val actual = arithm.evaluateExpr("(12 + 4 * 6) * ((2 + 3 * ( 4 + 2 ) ) * ( 5 + 12 ))")
val expected: Double = (12 + 4 * 6) * ((2 + 3 * ( 4 + 2 ) ) * ( 5 + 12 ))
assert(actual == expected)
提供的算术表达式的等效表达式树或解析树将属于 Parser[List[String]] 类型。
更多详情在以下链接:
http://nicolaecaralicea.blogspot.ca/2013/04/scala-dsl-for-parsing-and-evaluating-of.html
【讨论】:
【参考方案6】:除了动态 Linq 库(构建强类型表达式并需要强类型变量)之外,我推荐更好的替代方案:NReco Commons Library 的一部分的 linq 解析器(开源)。它对齐所有类型并在运行时执行所有调用,并且表现得像动态语言:
var lambdaParser = new NReco.LambdaParser();
var varContext = new Dictionary<string,object>();
varContext["one"] = 1M;
varContext["two"] = "2";
Console.WriteLine( lambdaParser.Eval("two>one && 0<one ? (1+8)/3+1*two : 0", varContext) ); // --> 5
【讨论】:
【参考方案7】:虽然这是相对较旧的帖子 - 这是表达式生成器的代码:AnyService - ExpressionTreeBuilder 这些是单元测试:AnyService - ExpressionTreeBuilder Unit Tests
【讨论】:
以上是关于如何将字符串转换为其等效的 LINQ 表达式树?的主要内容,如果未能解决你的问题,请参考以下文章
将objective-c typedef转换为其等效的字符串
将整数转换为其等效字符,其中 0 => a、1 => b 等