【问题标题】:System.Linq.Dynamic.DynamicExpression parsing expressions with methodsSystem.Linq.Dynamic.DynamicExpression 用方法解析表达式
【发布时间】:2015-07-27 12:56:11
【问题描述】:

我需要构建一个系统,其中有许多表达式存储在一个文件中。这些表达式将被读入程序,编译成 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”)将被使用,它看起来是可行的。从这样的表达式创建语法树的最佳方式/最佳代码是什么?我需要一些在符号未知时不会引发异常的东西(例如“ComplicatedFunction”),并且可以让我轻松解析树以构建表达式。

谢谢。

-------- 更新 -------------- -------

@pil0t 的回答让我走上了解决这个问题的正确轨道。基本上,您必须下载 Dynamic Linq 的原始源文件并进行两次修改。

寻找:

static readonly Type[] predefinedTypes = {

并删除“只读”关键字(以便对其进行修改)。

然后,将以下函数添加到 ExpressionParser 类中:

    public static void AddPredefinedType(Type type)
    {
        predefinedTypes = predefinedTypes.Union(new[] { type }).ToArray();
        keywords = ExpressionParser.CreateKeywords();
    }

您可以像这样使用新功能:

using System.Linq.Dynamic;
...
ExpressionParser.AddPredefinedType(typeof(GeoObj));

关于表达式语法的一个注释。新添加的功能将使您可以访问“Ge​​oObj”类(或您使用的任何类)的方法,并且只能访问这些方法。这意味着您必须按如下方式修改表达式:

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

换句话说,您需要使用的所有函数都需要在类中移动,就像@Shlomo 暗示的那样。我想要一个更简洁的符号(例如,使用“ComplicatedMethod(x,y)”而不是“GeoObj.ComplicatedMethod(x,y)”,或者使用类似 Method1(x) 而不是 x.Method1())在这一点上,这些主要是审美偏好。此外,由于可以使用的此类方法的数量很少,因此可以在内部重写表达式以从类函数调用表达式转变为方法调用。

感谢大家为我指明正确的方向。

【问题讨论】:

  • 也许您可以在表达式参数中将函数作为委托传递?

标签: c# linq expression-trees dynamic-linq


【解决方案1】:

我已经通过修补原始资源来做到这一点

public class Foo
{
...
        public static void AddPredefinedType(Type type)
        {
            ExpressionParser.predefinedTypes = ExpressionParser.predefinedTypes.Union(new[] { type }).ToArray();
            ExpressionParser.CreateKeywords();
        }
}

在此之后,您可以使用

Foo.AddPredefinedType(typeof(Program));

Program 中的所有方法都将可用

【讨论】:

  • 感谢您的建议,@pil0t。但是,我很难理解修复。这个“Foo”类是什么?我已将您建议的函数直接添加到 ExpressionParser(因为 'predefinedTypes' 不是公共的),并将代码用于 AddPredefinedType(typeof(Program))。但是,执行后,我得到了相同的结果。 “predefinedTypes”确实包含一种额外的类型,即“Program”,但其中没有任何方法。你能澄清一下答案吗?
【解决方案2】:

在DynamicExpression 的问题上,我真的无法为您提供帮助。如果您正在寻找如何构建表达式,这可能会帮助您入门。

假设您的班级 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 在功能上是等效的。

【讨论】:

  • 谢谢你,@Shlomo。如果我无法弄清楚如何动态解析字符串表达式,这肯定会让我开始。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-02
  • 2017-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
相关资源
最近更新 更多