문자열을 동등한 LINQ Expression Tree로 변환하는 방법은 무엇입니까?


173

이것은 원래 문제의 단순화 된 버전입니다.

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

이 문자열과 객체 인스턴스를 가져 와서 TRUE 또는 FALSE를 평가하고 싶습니다. 즉, 객체 인스턴스에서 Func <Person, bool>을 평가합니다.

내 현재 생각은 다음과 같습니다.

  1. 기본 비교 및 ​​논리 연산자를 지원하기 위해 ANTLR에서 기본 문법을 구현하십시오. Visual Basic 우선 순위와 일부 기능 집합을 여기에 복사하려고합니다. http://msdn.microsoft.com/en-us/library/fw84t893(VS.80).aspx
  2. ANTLR이 제공된 문자열에서 적절한 AST를 작성하도록하십시오.
  3. AST를 걷고 Predicate Builder 프레임 워크를 사용하여 Func <Person, bool>을 동적으로 작성하십시오.
  4. 필요에 따라 Person 인스턴스에 대한 술어 평가

내 질문은 내가 완전히 오버 베이크 한 것입니까? 대안이 있습니까?


편집 : 선택한 솔루션

동적 Linq 라이브러리, 특히 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 유형이며이 경우 TRUE입니다.

Marc Gravell에게 감사드립니다.

System.Linq.Dynamic 너겟 패키지, 문서를 여기에 포함 하십시오


33
질문과 함께 전체 솔루션 코드를 게시 해 주셔서 감사합니다. 매우 감사.
Adrian Grigore

컬렉션이나 사람들이 있고 일부 요소를 필터링하려면 어떻게해야합니까? Person.Age> 3 AND Person.Weight> 50?
serhio

감사. DynamicExpression.ParseLambda ()를 찾을 수 없습니다. 어떤 네임 스페이스와 어셈블리가 있습니까?
매트 피츠 모리스

좋습니다. 네임 스페이스간에 모호성이있었습니다. 필요-E = System.Linq.Expressions 사용; System.Linq.Dynamic 사용;
매트 피츠 모리스

왜 '&&'대신 'AND'를 사용합니까? C # 코드가 아닌가?
Triynko

답변:


65

겠습니까 라이브러리 LINQ 동적 여기에 도움을? 특히, 나는 Where절로 생각하고 있습니다. 필요한 경우 목록 / 배열 안에 넣으면 .Where(string)됩니다. 즉

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

그렇지 않다면, 파서 ( Expression후드에서 사용)를 작성하는 것은 큰 과세가 아닙니다. 크리스마스 바로 전에 기차 통근에 비슷한 것을 썼습니다 (내가 소스를 가지고 있다고 생각하지는 않지만).


"파서 작성 (후식 아래 식 사용)"을 구문 분석 한 다음 식 트리를 생성하거나 System.Linq.Expressions에 구문 분석 메커니즘이 있다는 것은 무슨 의미입니까?
AK_

나는 그가 그렇게 표현 된 식으로 파일을 읽고 그것을 술어로 번역하고 컴파일하기를 원한다고 확신합니다. 질문은 문법이 'string'에서 '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 좋다!
대기 시간

31

또 다른 도서관은 도망입니다

Dynamic Linq LibraryFlee and Flee 의 빠른 비교 를 통해 표현 속도가 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();
}

어쩌면 내가 잃어버린 것 같지만 linq 표현 트리를 만드는 데 '탈주'가 어떻게 도움이됩니까?
Michael B Hildebrand

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

LinqPadDump()방법이 있습니다


GetProperty 메소드는 어디에 있습니까?
Alen.Toma

@ Alen.Toma var type = typeof(T); var prop = type.GetProperty(propName);컴파일하기 위해 코드를 변경 해야했습니다.
Giles Roberts

이 컴파일 및 출력 덤프 제작
아 미트

5

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

물론이 기법은 런타임 평가를 기반으로하며 컴파일 타임에 코드를 확인할 수 없습니다.


1
'불량 코드'의 실행을 방지하고 싶습니다 ... 이것이 적합합니까?
Codebrain

'나쁜 코드'는 무엇을 의미합니까? 잘못된 표현식을 입력 한 사람이 있습니까? 이 경우 스크립트를 평가할 때 런타임 예외가 발생합니다.
Darin Dimitrov

@darin, 프로세스 시작, 데이터 변경 등과 같은 것들
sisve

2
'bad code'= Func <Person, bool> 유형의 표현이 아닌 것 (예 : 디스크에서 파일 삭제, 프로세스 회전 등)
Codebrain

1

다음은 산술 표현식의 구문 분석 및 평가를위한 Scala DSL 기반 파서 결합기의 예입니다.

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


0

Dynamic Linq Library (강력한 형식의 표현식을 작성하고 강력한 형식의 변수가 필요함) 외에도 NReco Commons Library의 일부인 linq 파서 (오픈 소스)를 사용 하는 것이 좋습니다 . 모든 유형을 정렬하고 런타임에 모든 호출을 수행하며 동적 언어처럼 작동합니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.