System.Linq.Dynamic.DynamicExpression analizar expresiones con métodos

c# dynamic-linq expression-trees linq

Pregunta

Necesito crear un sistema en el que tenga varias expresiones almacenadas en un archivo. Estas expresiones se leerían en el programa, se compilarían en expresiones linq y se evaluarían como funciones en varios objetos. Sin embargo, estas expresiones contendrían referencias a algunas funciones en el código (es decir, no estarían hechas solo de operadores básicos de C #).

Estoy tratando de usar DynamicExpression de System.Linq.Dynamic y casi funciona, excepto que no se reconocen mis funciones del código. Básicamente, cuando tengo este código a continuación:

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

(Por supuesto, en la versión final, sExpr se leerá de un archivo de texto, pero esto fue solo un ejemplo).

El código lanza una excepción en ParseLambda, indicando que "ComplicatedFunction" no se conoce. Puedo ver cómo ParseLambda no sabría acerca de "ComplicatedFunction" porque no sabe en qué ensamblaje se usa.

¿Hay una manera de compilar esa cadena en una expresión real? Si creo la expresión en mi código usando algo codificado de esta manera:

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

funciona como pretendía, pero no puedo anticipar todas las expresiones posibles.

Si no es posible, supongo que lo mejor es analizar las expresiones de cadena en un árbol de análisis y crear la expresión a partir de ese árbol. Como solo se utilizan unas pocas funciones (como "ComplicatedFunction"), parece factible. ¿Cuál es la mejor manera / el mejor código para crear el árbol de sintaxis a partir de tal expresión? Necesitaría algo que no arrojara excepciones cuando no se conozca un símbolo (como "ComplicatedFunction") y me facilitaría analizar el árbol para construir la expresión.

Gracias.

-------------------- ACTUALIZACIÓN ----------------------------- ----

La respuesta de @Pil0t me puso en el camino correcto para resolver esto. Básicamente, debes descargar el archivo fuente original para Dynamic Linq y hacer dos modificaciones.

Buscar:

static readonly Type[] predefinedTypes = {

y elimine la palabra clave 'readonly' (para que pueda ser modificada).

Luego, agregue la siguiente función a la clase ExpressionParser:

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

Puedes usar la nueva función así:

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

Una nota sobre la sintaxis de las expresiones sin embargo. La función recién agregada le dará acceso a los métodos de la clase 'GeoObj' (o la clase que use), y solo a esos. Esto significa que tendrás que modificar la expresión de la siguiente manera:

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

En otras palabras, todas las funciones que necesita usar deben moverse dentro de la clase, como @Shlomo insinuó. Me hubiera gustado tener una notación más limpia (por ejemplo, usar "ComplicatedMethod (x, y)" en lugar de "GeoObj.ComplicatedMethod (x, y)", o tener algo como Method1 (x) en lugar de x.Method1 ()), pero estas son en su mayoría preferencias estéticas en este punto. Además, como el número de métodos que se puede usar es pequeño, las expresiones se pueden reescribir internamente para pasar de las expresiones de función-llamada-como a las de método.

Gracias a todos por señalarme en la dirección correcta.

Respuesta aceptada

Lo he hecho parcheando fuentes originales con

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

después de esto, podrías usar

Foo.AddPredefinedType(typeof(Program));

y todos los métodos del Programa estarán disponibles


Respuesta popular

Realmente no puedo ayudarte con las cosas de DynamicExpression . Si estás buscando cómo construir Expresiones, esto podría ayudarte a comenzar.

Suponiendo que su clase GeoObj ve así:

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

    public object layer { get; set; }
}

Así es como construirías una expresión similar a la de tu ejemplo:

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 y expr2 son funcionalmente equivalentes.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow