Как преобразовать строку в эквивалентное дерево выражений LINQ?

StackOverflow https://stackoverflow.com/questions/821365

Вопрос

Это упрощенная версия исходной задачи.

У меня есть класс с именем Person:

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

... и давайте скажем экземпляр:

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

Я хотел бы написать следующее в виде строки в моем любимом текстовом редакторе ....

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

Я хотел бы взять эту строку и мой экземпляр объекта и оценить значение ИСТИНА или ЛОЖЬ - то есть вычисление Func < Person, bool > на экземпляре объекта.

Вот мои нынешние мысли:

<Ол>
  • Реализация базовой грамматики в ANTLR для поддержки базовых операторов сравнения и логических операторов. Я подумываю о копировании приоритета Visual Basic и некоторых наборов функций здесь: Predicate Builder для динамического создания Func lt; Person, bool >
  • Оцените предикат для экземпляра Person по мере необходимости
  • Мой вопрос: я полностью переварила это? какие-нибудь альтернативы?

    <Ч>

    РЕДАКТИРОВАТЬ: Выбранное решение

    Я решил использовать динамическую библиотеку Linq, в частности класс Dynamic Query, предоставленный в LINQSamples.

    Код ниже:

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

    Результат имеет тип System.Boolean, и в данном случае это ИСТИНА.

    Большое спасибо Марку Гравеллу.

    Включите System.Linq.Dynamic пакет nuget, документацию здесь

    Это было полезно?

    Решение

    Будет ли динамическая библиотека linq помогите здесь? В частности, я думаю как Where предложение. Если необходимо, поместите его в список / массив, чтобы вызвать .Where(string)! то есть.

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

    Если нет, то написание парсера (используя Expression под колпаком) не очень обременительно - я написал один подобный (хотя я не думаю, что у меня есть источник) в моем поезде, перед самым рождеством ...

    Другие советы

    Еще одна такая библиотека - Бегство

    Я быстро сравнил динамическую библиотеку Linq и Беги и Бежал был в 10 раз быстрее для выражения "(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"

    Вот как вы можете написать свой код, используя 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();
    }
    
    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 имеет метод Dump()

    Вы можете взглянуть на DLR . Это позволяет вам оценивать и выполнять скрипты внутри приложения .NET 2.0. Вот пример с 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; }
    }
    

    Конечно, этот метод основан на оценке во время выполнения, и код не может быть проверен во время компиляции.

    Ниже приведен пример комбинатора синтаксического анализа на основе DSL для Scala для анализа и вычисления арифметических выражений.

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

    Эквивалентное дерево выражений или дерево разбора предоставленного арифметического выражения будет иметь тип Parser [List [String]].

    Более подробную информацию можно найти по следующей ссылке:

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

    В дополнение к динамической библиотеке Linq (которая строит строго типизированное выражение и требует строго типизированных переменных), я рекомендую лучшую альтернативу: синтаксический анализатор linq, который является частью Библиотека NReco Commons (с открытым исходным кодом). Он выравнивает все типы и выполняет все вызовы во время выполнения и ведет себя как динамический язык:

    var lambdaParser = new NReco.LambdaParser();
    var varContext = new Dictionary<string,object>();
    varContext["one"] = 1M;
    varContext["two"] = "2";
    
    Console.WriteLine( lambdaParser.Eval("two>one && 0<one ? (1+8)/3+1*two : 0", varContext) ); // --> 5
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top