Wenden Sie ein dynamisch erstelltes Lambda auf eine Objektinstanz an

c# expression expression-trees lambda

Frage

Ich habe einen Code, der dynamisch ein Lambda ausgehend von Strings erzeugt. Zum Beispiel habe ich eine Filterklasse wie die folgende:

public class Criteria {
    public string Property { get; set; }
    public string Operator { get; set; }
    public string Value { get; set; }
}

Und ich bin in der Lage, ein Lambda wie x => x.Name == "Foo" zu erstellen, beginnend mit einer Criteria-Instanz wie dieser

Criteria c = new Criteria() {
    Property = "Name",
    Operator = "equal",
    Value = "Foo"
}

Angenommen, ich hätte eine Klasse wie

public class Receipt {
    public string Name { get; set; }
    public int Amount { get; set; }
    [other props omitted]
    public ICollection<ReceiptDetail> Details { get; set; }
}

Ich würde gern:

  1. Wenden Sie das Lambda auf jedes Objekt an (ich weiß, dass das Lambda mit einem ParameterExpression der Quittungsklasse erstellt werden soll)
  2. Rückgabe des booleschen Ergebnisses des Lambda (zB Ist der Name gleich Foo?)
  3. Wenden Sie die gleiche Logik auf die Count () - Methode des Collections an (zB Erstellen eines Lambda, das gegen receipt.Details.Count () prüft

Ist das möglich?

EDIT : Nach den Kommentaren, ich bin meine Bedürfnisse ein wenig mehr erarbeiten. Dieser Code gibt mir die Möglichkeit, auf eine Anforderung zu antworten, die ich habe und die besagt: Wenn für mein Objekt eine Regel angegeben ist, sollte sich die Anwendung ein wenig anders verhalten. Obwohl dies eine allgemeine Anforderung ist, möchte ich einen Code erstellen, der es mir erlaubt, ihn zu erweitern, da mehr Regeln hinzugefügt werden. Eigentlich habe ich nur 5 Regeltypen:

  • Überprüfen Sie, ob die Eingabe an einem bestimmten Wochentag erfolgt
  • Überprüfen Sie, ob die Eingabe in einem bestimmten Zeitraum erfolgt
  • Überprüfen Sie, ob das Feld "X" der Eingabe kleiner / gleich / größer als ein Wert ist
  • Überprüfen Sie, ob das Feld "Y" der Eingabe einen Wert enthält
  • Überprüfen Sie, ob das Feld "Z" der Eingabe, bei der es sich um eine Auflistung handelt, eine Anzahl kleiner / gleich / größer als ein Wert aufweist

Für die ersten 4 Punkte war ich in der Lage, dynamisch einen Lambda-Ausdruck mit einem Code wie in P.Brian.Mackey zu erstellen , den ich mit dem Spezifikationsmuster auf das Objekt selbst anwenden konnte.

Der letzte Punkt muss fast auf die gleiche Weise implementiert werden, aber der einzige Unterschied besteht darin, dass der linke Teil des Ausdrucks ein Methodenaufruf und keine Eigenschaft war (speziell die ICollection<T>.Count() -Methode).

Akzeptierte Antwort

Hier ist etwas, mit dem Sie beginnen können. Es gibt viel Raum für Verbesserungen. Vor allem die hässliche Fabrik. Diese Demo soll zeigen, wie Ausdrücke verwendet werden können, um die Probleme zu lösen, nicht als Best Practices- oder Factory-Pattern-Demo. Wenn etwas unklar ist, fragen Sie bitte nach einer Klarstellung.

Verwendung

    [Test]
    public void ApplySameLogicToCollectionsCount()
    {
        var receipt = new Receipt();
        var details = new ReceiptDetail();
        var details2 = new ReceiptDetail();
        receipt.Details.Add(details);
        receipt.Details.Add(details2);
        var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
        Assert.IsTrue(result(receipt.Details));
    }

Fabrik

 public static class LambdaGeneratorFactory<T>
    {
        //This is an ugly implementation of a Factory pattern.
        //You should improve this possibly with interfaces, maybe abstract factory.  I'd start with an ICriteria.
        public static Predicate<T> Run(Criteria criteria)
        {
            if (typeof(T) == typeof (Receipt))
            {
                return CreateLambda(criteria);
            }
            else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
            {
                return CreateLambdaWithCount(criteria);
            }

            return null;
        }
        private static Predicate<T> CreateLambda(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }

        private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
            Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }
    }

Kriterien

    private Criteria detailsCount = new Criteria()
    {
        Property = "Details",
        Operator = "equal",
        Value = "2"
    };

ICriteria zu ICriteria und die Dinge werden sauberer. Eine bessere Fabrik und keine Notwendigkeit für ToString . Programm zu einer Schnittstelle.

Alles was gesagt wird, fühlt sich dieser Code ein bisschen funky an. Was bringt es, Funktionen aus Strings zu generieren? Ich habe das Gefühl, dass dies C # aus einer Grammatik generiert. Ich bin nicht überzeugt, dass das gut skalieren wird. Für nicht-triviale Implementierungen betrachten Sie zuerst lex / yacc . Näheres dazu finden Sie im Pragmatischen Programmierer "Implementieren einer Minisprache".


Beliebte Antwort

Ihre Frage ist faszinierend und ich möchte die Anforderungen verstehen. Ich habe eine Demo erstellt, und ich frage mich, wie unterscheidet sich die Demo von dem, was Sie erreichen möchten? Es gibt eine funktionierende Version hier https://dotnetfiddle.net/AEBZ1w auch.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        Criteria c = new Criteria() { 
            Property = "Name", 
            Operator = "==", 
            Value = "Foo" };

        var queryable = (new List<Receipt>() { 
            new Receipt { Name = "Foo", Amount = 1 },
            new Receipt { Name = "Foo", Amount = 2 }, 
            new Receipt { Name = "Bar" }  
        }).AsQueryable();

        var parameter = Expression.Parameter(typeof(Receipt), "x");
        var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
        var constant = Expression.Constant(c.Value);
        var operation = Expression.Equal(property, constant);
        var expression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryable.ElementType },
            queryable.Expression, 
            Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
        );

        Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
        Console.WriteLine("Results: \n");

        var results = queryable.Provider.CreateQuery<Receipt>(expression);
        foreach(var r in results)
        {
            Console.WriteLine("{0}:{1}", r.Name, r.Amount);
        }
    }
}

public class Criteria
{
    public string Property, Operator, Value;
}

public class ReceiptDetail
{
    public string ItemName;
}

public class Receipt
{
    public string Name { get; set; }
    public int Amount;
    public ICollection<ReceiptDetail> Details;
}

Verweise



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum