Làm cách nào để chuyển đổi một Chuỗi thành Cây biểu thức LINQ tương đương của nó?


173

Đây là một phiên bản đơn giản hóa của vấn đề ban đầu.

Tôi có một lớp gọi là Person:

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

... và cho phép nói một ví dụ:

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

Tôi muốn viết đoạn dưới đây như một chuỗi trong trình soạn thảo văn bản yêu thích của tôi ....

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

Tôi muốn lấy chuỗi này và đối tượng của tôi và đánh giá TRUE hoặc FALSE - tức là đánh giá Func <Person, bool> trên đối tượng.

Dưới đây là những suy nghĩ hiện tại của tôi:

  1. Thực hiện một ngữ pháp cơ bản trong ANTLR để hỗ trợ các toán tử logic và so sánh cơ bản. Tôi đang nghĩ đến việc sao chép quyền ưu tiên Visual Basic và một số tính năng tại đây: http://msdn.microsoft.com/en-us/l Library / fw84t893 (VS.80) .aspx
  2. Yêu cầu ANTLR tạo AST phù hợp từ chuỗi được cung cấp.
  3. Đi bộ AST và sử dụng Trình tạo dự đoán khung Trình tạo dự để tự động tạo Func <Person, bool>
  4. Đánh giá vị ngữ theo trường hợp của Người theo yêu cầu

Câu hỏi của tôi là tôi đã hoàn toàn vượt qua điều này? có phương án nào không?


EDIT: Giải pháp chọn

Tôi đã quyết định sử dụng Thư viện Linq động, cụ thể là lớp Truy vấn động được cung cấp trong LINQSamples.

Mã dưới đây:

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

Kết quả là kiểu System.Boolean và trong trường hợp này là TRUE.

Rất cám ơn Marc Gravell.

Bao gồm gói nuget System.Linq.Docate , tài liệu tại đây


33
Cảm ơn bạn đã đăng mã giải pháp đầy đủ cùng với câu hỏi của bạn. Nhiều đánh giá cao.
Adrian Grigore

Điều gì nếu bạn có một bộ sưu tập hoặc người và muốn lọc một số yếu tố? Person.Age> 3 VÀ Person.ight> 50?
serhio

Cảm ơn. Tôi không thể tìm thấy DynamicExpression.PudeLambda (). Không gian tên và lắp ráp nào trong đó?
Matt Fitzmaurice

Tất cả đều tốt .. Có một sự mơ hồ giữa các không gian tên. Cần thiết - sử dụng E = System.Linq.Expressions; sử dụng System.Linq.Docate;
Matt Fitzmaurice

Tại sao nó sử dụng 'VÀ' thay vì '&&'. Nó không phải là mã C # sao?
Triynko

Câu trả lời:


65

Sẽ là động LINQ thư viện trợ giúp ở đây? Cụ thể, tôi đang nghĩ như một Wheređiều khoản. Nếu cần thiết, đặt nó trong một danh sách / mảng chỉ để gọi .Where(string)nó! I E

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

Nếu không, viết một trình phân tích cú pháp (sử dụng Expressiondưới mui xe) sẽ không đánh thuế mạnh - tôi đã viết một trình tương tự (mặc dù tôi không nghĩ rằng tôi có nguồn) trong chuyến tàu của tôi đi lại ngay trước khi xmas ...


Đánh dấu ý của bạn bằng cách "viết một trình phân tích cú pháp (sử dụng Biểu thức dưới mui xe)" Phân tích cú pháp và sau đó tạo một cây biểu thức, hoặc System.Linq.Expressions có một số cơ chế phân tích cú pháp?
AK_

Tôi khá chắc chắn rằng anh ấy đang muốn đọc trong một tập tin với biểu thức được hình thành như vậy và sau đó nó được dịch thành một vị ngữ và được biên dịch. Câu hỏi dường như là ,, làm cho ngữ pháp của bạn được chuyển đổi từ 'chuỗi' thành 'vị ngữ'. // 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 tốt!
Độ trễ

31

Một thư viện như vậy là Flee

Tôi đã làm một so sánh nhanh về Thư viện Linq độngFlee và Flee nhanh hơn 10 lần cho biểu thức"(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"

Đây là cách bạn có thể viết mã bằng 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();
}

Có lẽ tôi đang thiếu một cái gì đó, nhưng 'chạy trốn' giúp đỡ như thế nào trong việc xây dựng cây biểu thức 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()phương pháp


Phương pháp GetProperty ở đâu?
Alen.Toma

@ Alen.Toma Tôi đã phải thay đổi mã var type = typeof(T); var prop = type.GetProperty(propName);để biên dịch nó.
Giles Roberts

Làm cho nó biên dịch và đưa ra một đầu ra
Amit

5

Bạn có thể xem DLR . Nó cho phép bạn đánh giá và thực thi các tập lệnh bên trong ứng dụng .NET 2.0. Đây là một mẫu với 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; }
}

Tất nhiên kỹ thuật này dựa trên đánh giá thời gian chạy và mã không thể được xác minh tại thời điểm biên dịch.


1
Tôi muốn có thể bảo vệ chống lại việc thực thi 'mã xấu' ... điều này có phù hợp không?
Codebrain

"Mã xấu" nghĩa là gì? Ai đó gõ một biểu thức không hợp lệ? Trong trường hợp này, bạn sẽ nhận được một ngoại lệ thời gian chạy khi cố gắng đánh giá tập lệnh.
Darin Dimitrov

@darin, những thứ như bắt đầu quá trình, thay đổi dữ liệu, v.v.
sisve

2
'mã xấu' = thứ gì đó không phải là biểu thức của loại Func <Person, bool> (ví dụ: xóa các tệp khỏi đĩa, quay vòng quá trình, v.v.)
Codebrain

1

Dưới đây là một ví dụ về bộ kết hợp bộ phân tích cú pháp dựa trên Scala DSL để phân tích và đánh giá các biểu thức số học.

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

Cây biểu thức tương đương hoặc cây phân tích của biểu thức số học được cung cấp sẽ thuộc loại Parser [List [String]].

Thông tin chi tiết có tại liên kết sau:

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


0

Ngoài Thư viện Linq động (xây dựng biểu thức được gõ mạnh và yêu cầu các biến được gõ mạnh) Tôi khuyên bạn nên thay thế tốt hơn: trình phân tích cú pháp linq là một phần của Thư viện NReco Commons (nguồn mở). Nó căn chỉnh tất cả các loại và thực hiện tất cả các yêu cầu trong thời gian chạy và hoạt động như ngôn ngữ động:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.