System.Linq.Dynamic.DynamicExpression 用方法解析表达式
System.Linq.Dynamic.DynamicExpression parsing expressions with methods
我需要构建一个系统,其中有许多存储在文件中的表达式。这些表达式将被读入程序,编译成 linq 表达式并作为函数对许多对象进行评估。但是,这些表达式将包含对代码中某些函数的引用(即它们不会仅由基本的 C# 运算符组成)。
我正在尝试使用 System.Linq.Dynamic 中的 DynamicExpression,它几乎可以工作,除了无法识别我的代码函数。基本上,当我有以下代码时:
public class GeoObj
{
public int layer;
public Polygon poly;
}
class Program
{
public static bool ComplicatedMethod(GeoObj x, GeoObj y)
{
bool result = // some quite complicated computation on x and y, say a polygon intersection test
return result;
}
static void Main(string[] args)
{
GeoObj o1 = new GeoObj(); // here set o1 fields
GeoObj o2 = new GeoObj(); // here set o2 fields
string sExpr = @"(x.layer == y.layer) && (ComplicatedMethod(x,y))";
var xparam = Expression.Parameter(typeof(GeoObj), "x");
var yparam = Expression.Parameter(typeof(GeoObj), "y");
Expression<Func<GeoObj, GeoObj, bool>> expr = (Expression<Func<GeoObj, GeoObj, bool>>)System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { xparam, yparam }, typeof(bool), sExpr);
var del2 = expr.Compile();
Console.WriteLine(del2(o1, o2));
}
}
(当然,在最终版本中,sExpr 会从文本文件中读取,但这只是作为示例。)
代码在 ParseLambda 处抛出异常,指出 "ComplicatedFunction" 未知。我可以看出 ParseLambda 如何不知道 "ComplicatedFunction" 因为它不知道它在哪个程序集中使用。
有没有办法将该字符串编译成实际的表达式?如果我使用像这样的硬编码在我的代码中创建表达式:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && ComplicatedFunction(x,y));
它按我的预期工作,但我无法预料所有可能的表达式。
如果不可能,我想下一个最好的办法是将字符串表达式解析为解析树,然后从该树创建表达式。由于只有几个函数(如 "ComplicatedFunction")将被使用,因此它看起来可行。从这样的表达式创建语法树的最佳 way/best 代码是什么?我需要一些在符号未知时不会抛出异常的东西(比如 "ComplicatedFunction"),并且可以让我很容易地解析树来构建表达式。
谢谢。
--------------------更新------------------------ --------
@pil0t 的回答让我走上了解决这个问题的正确轨道。基本上,您必须下载 Dynamic Linq 的原始源文件并进行两次修改。
寻找:
static readonly Type[] predefinedTypes = {
并删除'readonly'关键字(以便修改)。
然后,将以下函数添加到 ExpressionParser class:
public static void AddPredefinedType(Type type)
{
predefinedTypes = predefinedTypes.Union(new[] { type }).ToArray();
keywords = ExpressionParser.CreateKeywords();
}
您可以像这样使用新功能:
using System.Linq.Dynamic;
...
ExpressionParser.AddPredefinedType(typeof(GeoObj));
关于表达式语法的一个注释。新添加的函数将使您能够访问 class 'GeoObj'(或您使用的任何 class)的方法,并且只能访问这些方法。这意味着您必须按如下方式修改表达式:
class GeoObj {
public bool field1;
public bool method1() { /* return something */ }
public static ComplicatedMethod(GeoObj o1, GeoObj o2) { ... }
}
...
string myExpr = @"x.field1 && y.method1() && GeoObj.ComplicatedMethod(x,y)";
换句话说,您需要使用的所有功能都需要移动到 class 中,就像@Shlomo 暗示的那样。我想要一个更清晰的符号(例如使用 "ComplicatedMethod(x,y)" 而不是 "GeoObj.ComplicatedMethod(x,y)",或者使用 Method1(x) 而不是 x.Method1() 之类的东西),但这些主要是此时的审美偏好.此外,由于可以使用的此类方法的数量很少,因此可以在内部重写表达式,从类似函数调用的表达式变为方法调用。
感谢大家给我指出正确的方向。
我已经通过使用
修补原始资源来做到这一点
public class Foo
{
...
public static void AddPredefinedType(Type type)
{
ExpressionParser.predefinedTypes = ExpressionParser.predefinedTypes.Union(new[] { type }).ToArray();
ExpressionParser.CreateKeywords();
}
}
在此之后,您可以使用
Foo.AddPredefinedType(typeof(Program));
程序中的所有方法都可用
在 DynamicExpression
方面我真的帮不了你。如果您正在寻找如何构建表达式,这可能有助于您入门。
假设您的 class GeoObj
看起来像这样:
class GeoObj
{
public static bool ComplicatedFunction(GeoObj a, GeoObj b)
{
return false;
}
public object layer { get; set; }
}
以下是构建与示例中的表达式类似的表达式的方法:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && GeoObj.ComplicatedFunction(x,y);
var complicatedFunctionMethodInfo = typeof (GeoObj).GetMethod("ComplicatedFunction");
var paramX = Expression.Parameter(typeof (GeoObj), "x");
var paramY = Expression.Parameter(typeof (GeoObj), "y");
var expr2 = Expression.Lambda<Func<GeoObj, GeoObj, bool>>(
Expression.AndAlso
(
Expression.Equal
(
Expression.Property(paramX, "layer"),
Expression.Property(paramY, "layer")
),
Expression.Call(complicatedFunctionMethodInfo, paramX, paramY)
),
paramX,
paramY
);
expr
和 expr2
在功能上是等价的。
我需要构建一个系统,其中有许多存储在文件中的表达式。这些表达式将被读入程序,编译成 linq 表达式并作为函数对许多对象进行评估。但是,这些表达式将包含对代码中某些函数的引用(即它们不会仅由基本的 C# 运算符组成)。
我正在尝试使用 System.Linq.Dynamic 中的 DynamicExpression,它几乎可以工作,除了无法识别我的代码函数。基本上,当我有以下代码时:
public class GeoObj
{
public int layer;
public Polygon poly;
}
class Program
{
public static bool ComplicatedMethod(GeoObj x, GeoObj y)
{
bool result = // some quite complicated computation on x and y, say a polygon intersection test
return result;
}
static void Main(string[] args)
{
GeoObj o1 = new GeoObj(); // here set o1 fields
GeoObj o2 = new GeoObj(); // here set o2 fields
string sExpr = @"(x.layer == y.layer) && (ComplicatedMethod(x,y))";
var xparam = Expression.Parameter(typeof(GeoObj), "x");
var yparam = Expression.Parameter(typeof(GeoObj), "y");
Expression<Func<GeoObj, GeoObj, bool>> expr = (Expression<Func<GeoObj, GeoObj, bool>>)System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { xparam, yparam }, typeof(bool), sExpr);
var del2 = expr.Compile();
Console.WriteLine(del2(o1, o2));
}
}
(当然,在最终版本中,sExpr 会从文本文件中读取,但这只是作为示例。)
代码在 ParseLambda 处抛出异常,指出 "ComplicatedFunction" 未知。我可以看出 ParseLambda 如何不知道 "ComplicatedFunction" 因为它不知道它在哪个程序集中使用。
有没有办法将该字符串编译成实际的表达式?如果我使用像这样的硬编码在我的代码中创建表达式:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && ComplicatedFunction(x,y));
它按我的预期工作,但我无法预料所有可能的表达式。
如果不可能,我想下一个最好的办法是将字符串表达式解析为解析树,然后从该树创建表达式。由于只有几个函数(如 "ComplicatedFunction")将被使用,因此它看起来可行。从这样的表达式创建语法树的最佳 way/best 代码是什么?我需要一些在符号未知时不会抛出异常的东西(比如 "ComplicatedFunction"),并且可以让我很容易地解析树来构建表达式。
谢谢。
--------------------更新------------------------ --------
@pil0t 的回答让我走上了解决这个问题的正确轨道。基本上,您必须下载 Dynamic Linq 的原始源文件并进行两次修改。
寻找:
static readonly Type[] predefinedTypes = {
并删除'readonly'关键字(以便修改)。
然后,将以下函数添加到 ExpressionParser class:
public static void AddPredefinedType(Type type)
{
predefinedTypes = predefinedTypes.Union(new[] { type }).ToArray();
keywords = ExpressionParser.CreateKeywords();
}
您可以像这样使用新功能:
using System.Linq.Dynamic;
...
ExpressionParser.AddPredefinedType(typeof(GeoObj));
关于表达式语法的一个注释。新添加的函数将使您能够访问 class 'GeoObj'(或您使用的任何 class)的方法,并且只能访问这些方法。这意味着您必须按如下方式修改表达式:
class GeoObj {
public bool field1;
public bool method1() { /* return something */ }
public static ComplicatedMethod(GeoObj o1, GeoObj o2) { ... }
}
...
string myExpr = @"x.field1 && y.method1() && GeoObj.ComplicatedMethod(x,y)";
换句话说,您需要使用的所有功能都需要移动到 class 中,就像@Shlomo 暗示的那样。我想要一个更清晰的符号(例如使用 "ComplicatedMethod(x,y)" 而不是 "GeoObj.ComplicatedMethod(x,y)",或者使用 Method1(x) 而不是 x.Method1() 之类的东西),但这些主要是此时的审美偏好.此外,由于可以使用的此类方法的数量很少,因此可以在内部重写表达式,从类似函数调用的表达式变为方法调用。
感谢大家给我指出正确的方向。
我已经通过使用
修补原始资源来做到这一点public class Foo
{
...
public static void AddPredefinedType(Type type)
{
ExpressionParser.predefinedTypes = ExpressionParser.predefinedTypes.Union(new[] { type }).ToArray();
ExpressionParser.CreateKeywords();
}
}
在此之后,您可以使用
Foo.AddPredefinedType(typeof(Program));
程序中的所有方法都可用
在 DynamicExpression
方面我真的帮不了你。如果您正在寻找如何构建表达式,这可能有助于您入门。
假设您的 class GeoObj
看起来像这样:
class GeoObj
{
public static bool ComplicatedFunction(GeoObj a, GeoObj b)
{
return false;
}
public object layer { get; set; }
}
以下是构建与示例中的表达式类似的表达式的方法:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && GeoObj.ComplicatedFunction(x,y);
var complicatedFunctionMethodInfo = typeof (GeoObj).GetMethod("ComplicatedFunction");
var paramX = Expression.Parameter(typeof (GeoObj), "x");
var paramY = Expression.Parameter(typeof (GeoObj), "y");
var expr2 = Expression.Lambda<Func<GeoObj, GeoObj, bool>>(
Expression.AndAlso
(
Expression.Equal
(
Expression.Property(paramX, "layer"),
Expression.Property(paramY, "layer")
),
Expression.Call(complicatedFunctionMethodInfo, paramX, paramY)
),
paramX,
paramY
);
expr
和 expr2
在功能上是等价的。