Bir String'i eşdeğer LINQ İfade Ağacı'na nasıl dönüştürebilirim?


173

Bu, orijinal sorunun basitleştirilmiş bir sürümüdür.

Kişi adında bir sınıfım var:

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

... ve bir örnek söyleyelim:

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

Ben en sevdiğim metin editörü aşağıdaki bir dize olarak yazmak istiyorum ....

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

Bu dize ve nesne örneğimi almak ve bir DOĞRU veya YANLIŞ - yani nesne örneği üzerinde bir Func <Kişi, bool> değerlendirmek istiyorum.

Şu anki düşüncelerim:

  1. Temel Karşılaştırma ve Mantıksal Operatörleri desteklemek için ANTLR'de temel bir dilbilgisi uygulayın. Visual Basic önceliğini ve bazı özellikleri burada kopyalamayı düşünüyorum: http://msdn.microsoft.com/en-us/library/fw84t893(VS.80).aspx
  2. ANTLR'nin sağlanan bir dizeden uygun bir AST oluşturmasını sağlayın.
  3. AST'yi yürütün ve Dinamik olarak Func <Kişi, bool> oluşturmak için Predicate Builder çerçevesini kullanın
  4. Kişinin bir örneğine karşı yüklemi gerektiği gibi değerlendirin

Sorum şu: Bu konuyu tamamen aşmış mıyım? alternatif var mı?


EDIT: Seçilen Çözüm

Dynamic Linq Library'yi, özellikle LINQSamples'ta sağlanan Dynamic Query sınıfını kullanmaya karar verdim.

Aşağıdaki kod:

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

Sonuç System.Boolean türünde ve bu örnekte TRUE.

Marc Gravell'e çok teşekkürler.

System.Linq.Dynamic nuget paketini, belgeleri buraya ekleyin


33
Sorunuzla birlikte tam çözüm kodunu gönderdiğiniz için teşekkür ederiz. Çok takdir etmek.
Adrian Grigore

bir koleksiyonunuz veya kişileriniz varsa ve bazı öğeleri filtrelemek istiyorsanız ne olur? Kişi Yaş> 3 VE Kişi Ağırlık> 50?
serhio

Teşekkürler. DynamicExpression.ParseLambda () öğesini bulamıyorum. Hangi isim alanında ve montajda?
Matt Fitzmaurice

Her şey yolunda .. İsim alanları arasında bir belirsizlik vardı. Gerekli - E = System.Linq.Expressions kullanarak; System.Linq.Dynamic kullanarak;
Matt Fitzmaurice

Neden '&&' yerine 'AND' kullanıyor? C # kodu olması gerekmez mi?
Triynko

Yanıtlar:


65

Misiniz kütüphane linq dinamik burada yardım? Özellikle, bir Wheremadde olarak düşünüyorum . Gerekirse, sadece onu çağırmak .Where(string)için bir liste / dizi içine koyun ! yani

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

Değilse, bir ayrıştırıcı ( Expressionbaşlık altında kullanarak ) yazmak çok vergilendirmez - bir tane yazdım (kaynağa sahip olduğumu düşünmeme rağmen) xmas'tan hemen önce trenime gidiyorum ...


"Bir ayrıştırıcı yazarak (başlık altında İfade kullanarak)" ne demek istediğinizi işaretleyin Ayrıştırma ve sonra bir ifade ağacı oluşturma veya System.Linq.Expressions'ın bazı ayrıştırma mekanizması var mı?
AK_

Eminim ki bu şekilde oluşturulmuş bir ifadeyle bir dosyada okumak istiyor ve daha sonra bir yüklem ve derlenmiş olarak tercüme edilmesini istiyor. Soru, `` dilbilgisinden '' yüklem '' dilbilgisine dönüştürülüyor gibi görünüyor. // 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 iyi!
Gecikme

31

Böyle bir kütüphane Flee

Dynamic Linq Library'nin hızlı bir karşılaştırmasını yaptım ve Flee ve Flee ifade için 10 kat daha hızlıydı"(Name == \"Johan\" AND Salary > 500) OR (Name != \"Johan\" AND Salary > 300)"

Kodunuzu Flee kullanarak bu şekilde yazabilirsiniz.

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

Belki bir şey eksik, ama 'kaçmak' bir linq ifade ağacı oluşturmak için nasıl yardımcı olur?
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;
    }
}

LINQPad sahip Dump()yöntemi


GetProperty yöntemi nerede?
Alen.Toma

@ Alen.Toma Derlemek için kodu değiştirmek zorunda kaldı var type = typeof(T); var prop = type.GetProperty(propName);.
Giles Roberts

Derleyin ve bir çıktı dökümü yaptı
Amit

5

DLR'ye bir göz atabilirsiniz . .NET 2.0 uygulaması içindeki komut dosyalarını değerlendirmenizi ve yürütmenizi sağlar. İşte IronRuby ile bir örnek :

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

Tabii ki bu teknik çalışma zamanı değerlendirmesine dayanmaktadır ve derleme zamanında kod doğrulanamaz.


1
Ben 'kötü kod' yürütülmesine karşı korumak istiyorum ... bu iyi bir uyum olurdu?
Codebrain

'Kötü kod' ile ne demek istiyorsun? Birisi geçerli olmayan bir ifade yazıyor mu? Bu durumda, komut dosyasını değerlendirmeye çalışırken bir çalışma zamanı istisnası alırsınız.
Darin Dimitrov

@darin, başlangıç ​​işlemleri, veri değiştirme vb. şeyler
sisve

2
'kötü kod' = Func <Kişi, bool> türünde bir ifade olmayan bir şey (örneğin, dosyaları bir diskten silmek, bir işlemi döndürmek vb.)
Codebrain

1

Aritmetik ifadelerin ayrıştırılması ve değerlendirilmesi için Scala DSL tabanlı ayrıştırıcı bir birleştirici örneği.

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

Sağlanan aritmetik ifadenin eşdeğer ifade ağacı veya ayrıştırma ağacı Parser [List [String]] türünde olacaktır.

Daha fazla ayrıntı aşağıdaki bağlantıda bulunmaktadır:

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


0

Dynamic Linq Library'ye ek olarak (güçlü bir şekilde yazılan ifade oluşturur ve güçlü bir şekilde yazılan değişkenler gerektirir) daha iyi bir alternatif öneriyorum: NReco Commons Library'nin (açık kaynak) bir parçası olan linq parser . Tüm türleri hizalar ve çalışma zamanında tüm çağrıları gerçekleştirir ve dinamik dil gibi davranır:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.