System.Linq.Dynamic.DynamicExpression parsing expr

2020-07-27 02:10发布

I need to build a system where I have a number of expressions that are stored in a file. These expressions would be read into the program, compiled into linq expressions and evaluated as functions on a number of objects. However, these expressions would contain references to some functions in the code (i.e. they would not be made of just basic C# operators).

I'm trying to use DynamicExpression from System.Linq.Dynamic and it almost works, except that my functions from the code are not recognized. Basically, when I have this code below:

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

(Of course, in the final version, sExpr would be read from a text file, but this was just as an example.)

The code throws an exception at ParseLambda, stating that "ComplicatedFunction" is not known. I can see how ParseLambda would not know about "ComplicatedFunction" because it does not know in which assembly it's used.

Is there a way to compile that string into an actual expression? If I create the expression in my code using something hard-coded like this:

Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && ComplicatedFunction(x,y));

it works as I intended, but I cannot anticipate all possible expressions.

If it's not possible, I suppose the next best thing is to parse the string expressions into a parse tree, and create the expression from that tree. Since there are only a few functions (like "ComplicatedFunction") that will be used, it looks doable. What is the best way/best code to create the syntax tree from such an expression? I would need something that would not throw exceptions when a symbol is not known (like "ComplicatedFunction") and would make it easy for me to to parse the tree to build the expression.

Thank you.

-------------------- UPDATE ---------------------------------

@pil0t 's answer put me on the right track to solving this. Basically, you have to download the original source file for Dynamic Linq and make two modifications.

Look for:

static readonly Type[] predefinedTypes = {

and remove the 'readonly' keyword (so it can be modified).

Then, add the following function to the ExpressionParser class:

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

You can use the new function like this:

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

One note about the syntax for the expressions though. The newly added function will give you access to the methods of the class 'GeoObj' (or whatever class you use), and only to those. This means that you'll have to modify the expression as follows:

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

In other words, all functions that you need to use need to be moved inside the class, like @Shlomo hinted at. I would've liked a cleaner notation (e.g. use "ComplicatedMethod(x,y)" instead of "GeoObj.ComplicatedMethod(x,y)", or have something like Method1(x) instead of x.Method1()), but these are mostly aesthetic preferences at this point. Besides, since the number of such methods that can be used is small, expressions can be rewritten internally to go from function-call-like-expressions to method calls.

Thanks everyone for pointing me in the right direction.

2条回答
男人必须洒脱
2楼-- · 2020-07-27 02:31

I have done this by patching original sources with

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

after this, you could use

Foo.AddPredefinedType(typeof(Program));

and all methods from Program will be available

查看更多
Melony?
3楼-- · 2020-07-27 02:33

I can't really help you with the DynamicExpression stuff. If you're looking for how to build Expressions, this might help get you started.

Assuming your class GeoObj looks like this:

class GeoObj
{
    public static bool ComplicatedFunction(GeoObj a, GeoObj b)
    {
        return false;
    }

    public object layer { get; set; }
}

Here's how you would build a similar expression to the one in your example:

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 and expr2 are functionally equivalent.

查看更多
登录 后发表回答