199 votes

Comment convertir un String en son équivalent LINQ Expression Tree ?

Il s'agit d'une version simplifiée du problème original.

J'ai une classe appelée Personne :

public class Person {
  public string Name { get; set; }
  public int Age { get; set; }
  public int Weight { get; set; }
  public DateTime FavouriteDay { get; set; }
}

...et disons une instance :

var bob = new Person {
  Name = "Bob",
  Age = 30,
  Weight = 213,
  FavouriteDay = '1/1/2000'
}

Je voudrais écrire ce qui suit comme un chaîne de caractères dans mon éditeur de texte préféré....

(Person.Age > 3 AND Person.Weight > 50) OR Person.Age < 3

Je voudrais prendre cette chaîne et mon instance d'objet et évaluer un VRAI ou un FAUX - c'est-à-dire évaluer un Func<Personne, bool> sur l'instance d'objet.

Voici ce que je pense actuellement :

  1. Implémenter une grammaire de base dans ANTLR pour supporter les comparaisons de base et les opérateurs logiques. Je pense copier la précédence de Visual Basic et certaines des caractéristiques ici : http://msdn.microsoft.com/en-us/library/fw84t893(VS.80).aspx
  2. Demandez à ANTLR de créer un AST approprié à partir d'une chaîne fournie.
  3. Marchez sur l'AST et utilisez le Constructeur de prédicats pour créer de manière dynamique le système Func<Person, bool>.
  4. Évaluer le prédicat contre une instance de Person comme requis

Ma question est de savoir si j'ai trop cuit ce plat ? Y a-t-il des alternatives ?


EDIT : Solution choisie

J'ai décidé d'utiliser la Dynamic Linq Library, plus précisément la classe Dynamic Query fournie dans les LINQSamples.

Code ci-dessous :

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

Le résultat est de type System.Boolean, et dans ce cas, il est VRAI.

Merci à Marc Gravell.

Inclure System.Linq.Dynamic paquet nuget, documentation ici

39 votes

Merci de poster le code complet de la solution avec votre question. Nous apprécions beaucoup.

0 votes

Que faire si vous avez une collection ou des personnes et que vous souhaitez filtrer certains éléments ? Personne.Age > 3 AND Personne.Poids > 50 ?

0 votes

Merci. Je ne trouve pas DynamicExpression.ParseLambda(). Dans quel espace de noms et dans quel assemblage se trouve-t-il ?

74voto

Marc Gravell Points 482669

Est-ce que le bibliothèque linq dynamique de l'aide ici ? En particulier, je pense qu'en tant que Where clause. Si nécessaire, placez-la dans une liste/un tableau afin d'appeler .Where(string) sur elle ! c'est-à-dire

var people = new List<Person> { person };
int match = people.Where(filter).Any();

Sinon, l'écriture d'un analyseur syntaxique (en utilisant Expression J'en ai écrit un semblable (bien que je ne pense pas en avoir la source) pendant mon trajet en train juste avant Noël...

1 votes

Mark, que voulez-vous dire par "écrire un analyseur (en utilisant Expression sous le capot)" ? Analyser et ensuite générer un arbre d'expression, ou est-ce que System.Linq.Expressions a un mécanisme d'analyse ?

0 votes

Je suis presque sûr qu'il veut lire un fichier avec l'expression formée comme telle, puis la faire traduire comme un prédicat et la compiler. La question semble être d'obtenir que votre grammaire soit convertie de 'string' en 'predicate'. // 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 bien !

0 votes

Celui-ci est un joyau de la couronne. J'ai cherché et cherché et essayé 6 solutions opensource différentes pendant quelques heures - aucune ne fonctionnait sans but et if et la taille de chaque dll était plusieurs fois plus grande que LINQ dynamic et LINQ que j'ai fait fonctionner pour moi en 20 minutes environ.

33voto

chikak Points 954

Une autre bibliothèque de ce type est Fuir

J'ai fait une comparaison rapide de Bibliothèque dynamique Linq y Fuir et Flee était 10 fois plus rapide pour l'expression "(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"

C'est ainsi que vous pouvez écrire votre code en utilisant 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();
}

0 votes

Peut-être que quelque chose m'échappe, mais en quoi 'flee' aide-t-il à construire un arbre d'expression linq ?

10voto

suneelsarraf Points 81
void Main()
{
    var testdata = new List<Ownr> {
        //new Ownr{Name = "abc", Qty = 20}, // uncomment this to see it getting filtered out
        new Ownr{Name = "abc", Qty = 2},
        new Ownr{Name = "abcd", Qty = 11},
        new Ownr{Name = "xyz", Qty = 40},
        new Ownr{Name = "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 a le Dump() méthode

0 votes

Où se trouve la méthode GetProperty ?

0 votes

@Alen.Toma J'ai dû changer le code en var type = typeof(T); var prop = type.GetProperty(propName); pour le faire compiler.

0 votes

Je l'ai fait compiler et sortir une sortie

5voto

Darin Dimitrov Points 528142

Vous pourriez jeter un coup d'œil à la DLR . Il vous permet d'évaluer et d'exécuter des scripts à l'intérieur d'une application .NET 2.0. Voici un exemple avec 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; }
}

Bien sûr, cette technique est basée sur l'évaluation à l'exécution et le code ne peut pas être vérifié au moment de la compilation.

1 votes

Je veux être en mesure de me protéger contre l'exécution d'un "mauvais code"... Est-ce que cela conviendrait ?

0 votes

Qu'entendez-vous par "mauvais code" ? Quelqu'un qui tape une expression qui n'est pas valide ? Dans ce cas, vous obtiendrez une exception d'exécution en essayant d'évaluer le script.

0 votes

@darin, des choses comme le lancement de processus, la modification de données, etc.

1voto

ncaralicea Points 11

Voici un exemple de combinateur d'analyseur basé sur un DSL Scala pour l'analyse et l'évaluation d'expressions arithmétiques.

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

L'arbre d'expression ou l'arbre d'analyse équivalent de l'expression arithmétique fournie serait du type Parser[List[String]].

Plus de détails sont disponibles sur le lien suivant :

http://nicolaecaralicea.blogspot.ca/2013/04/scala-dsl-for-parsing-and-evaluating-of.html

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X