Suche…


Bemerkungen

Ausdrucksbäume sind Datenstrukturen, die zur Darstellung von Codeausdrücken in .NET Framework verwendet werden. Sie können per Code generiert und programmgesteuert durchlaufen werden, um den Code in eine andere Sprache zu übersetzen oder auszuführen. Der bekannteste Generator von Expression Trees ist der C # -Compiler selbst. Der C # -Compiler kann Ausdrucksbäume generieren, wenn einer Variablen des Typs Ausdruck <Func <... >> ein Lambda-Ausdruck zugewiesen wird. Normalerweise geschieht dies im Zusammenhang mit LINQ. Der beliebteste Verbraucher ist der LINQ-Anbieter von Entity Framework. Es verbraucht die Ausdrucksbäume, die an Entity Framework übergeben werden, und generiert entsprechenden SQL-Code, der dann für die Datenbank ausgeführt wird.

Einfache Ausdrucksbaumstruktur, die vom C # -Compiler generiert wird

Betrachten Sie den folgenden C # -Code

Expression<Func<int, int>> expression = a => a + 1;

Da der C # -Compiler erkennt, dass der Lambda-Ausdruck einem Ausdruckstyp und nicht einem Delegatentyp zugeordnet ist, generiert er eine Ausdrucksstruktur, die diesem Code in etwa entspricht

ParameterExpression parameterA = Expression.Parameter(typeof(int), "a");
var expression = (Expression<Func<int, int>>)Expression.Lambda(
                                                 Expression.Add(
                                                     parameterA,
                                                     Expression.Constant(1)),
                                                 parameterA);

Die Wurzel des Baums ist der Lambda-Ausdruck, der einen Hauptteil und eine Liste von Parametern enthält. Das Lambda hat 1 Parameter namens "a". Der Hauptteil ist ein einzelner Ausdruck des CLR-Typs BinaryExpression und NodeType of Add. Dieser Ausdruck steht für die Addition. Es gibt zwei Unterausdrücke, die als Left und Right bezeichnet werden. Left ist die ParameterExpression für den Parameter "a" und Right ist eine ConstantExpression mit dem Wert 1.

Die einfachste Verwendung dieses Ausdrucks ist das Ausdrucken:

Console.WriteLine(expression); //prints a => (a + 1)

Welche gibt den entsprechenden C # -Code aus.

Der Ausdrucksbaum kann in einen C # -Delegierten kompiliert und von der CLR ausgeführt werden

Func<int, int> lambda = expression.Compile();
Console.WriteLine(lambda(2)); //prints 3

Normalerweise werden Ausdrücke in andere Sprachen wie SQL übersetzt, können aber auch zum Aufrufen von privaten, geschützten und internen Mitgliedern von öffentlichen oder nicht öffentlichen Typen als Alternative zu Reflection verwendet werden.

Erstellen eines Prädikats von Formularfeld == Wert

Um einen Ausdruck wie _ => _.Field == "VALUE" zur Laufzeit _ => _.Field == "VALUE" .

_ => _.Field ein Prädikat _ => _.Field und einen Zeichenfolgenwert "VALUE" einen Ausdruck, der prüft, ob das Prädikat wahr ist.

Der Ausdruck ist geeignet für:

  • IQueryable<T> , IEnumerable<T> , um das Prädikat zu testen.
  • Entity Framework oder Linq to SQL , um eine Where Klausel zu erstellen, die das Prädikat testet.

Diese Methode erstellt einen entsprechenden Equal Ausdruck, der prüft, ob Field gleich "VALUE" oder nicht.

public static Expression<Func<T, bool>> BuildEqualPredicate<T>(
    Expression<Func<T, string>> memberAccessor,
    string term)
{
    var toString = Expression.Convert(Expression.Constant(term), typeof(string));
    Expression expression = Expression.Equal(memberAccessor.Body, toString);
    var predicate = Expression.Lambda<Func<T, bool>>(
        expression,
        memberAccessor.Parameters);
    return predicate;
}

Das Prädikat kann verwendet werden, indem das Prädikat in eine Where Erweiterungsmethode eingeschlossen wird.

var predicate = PredicateExtensions.BuildEqualPredicate<Entity>(
    _ => _.Field,
    "VALUE");
var results = context.Entity.Where(predicate).ToList();

Ausdruck zum Abrufen eines statischen Feldes

Beispiel-Typ wie folgt eingeben:

public TestClass
{
    public static string StaticPublicField = "StaticPublicFieldValue";
}

Wir können den Wert von StaticPublicField abrufen:

var fieldExpr = Expression.Field(null, typeof(TestClass), "StaticPublicField");
var labmda = Expression.Lambda<Func<string>>(fieldExpr);

Es kann dann zB in einen Delegierten zum Abrufen des Feldwerts kompiliert werden.

Func<string> retriever = lambda.Compile();
var fieldValue = retriever();

// Das Ergebnis von fieldValue ist StaticPublicFieldValue

InvocationExpression-Klasse

Die InvocationExpression-Klasse ermöglicht das Aufrufen anderer Lambda-Ausdrücke, die Teile des gleichen Ausdrucksbaums sind.

Sie erstellen sie mit der statischen Expression.Invoke Methode.

Problem Wir möchten auf die Artikel zugreifen, die in ihrer Beschreibung "Auto" haben. Wir müssen den Wert auf null überprüfen, bevor Sie nach einer Zeichenfolge suchen. Wir möchten jedoch nicht, dass er übermäßig aufgerufen wird, da die Berechnung teuer sein kann.

using System;
using System.Linq;
using System.Linq.Expressions;
                    
public class Program
{
    public static void Main()
    {
        var elements = new[] {
            new Element { Description = "car" },
            new Element { Description = "cargo" },
            new Element { Description = "wheel" },
            new Element { Description = null },
            new Element { Description = "Madagascar" },
        };
    
        var elementIsInterestingExpression = CreateSearchPredicate(
            searchTerm: "car",
            whereToSearch: (Element e) => e.Description);
            
        Console.WriteLine(elementIsInterestingExpression.ToString());
            
        var elementIsInteresting = elementIsInterestingExpression.Compile();
        var interestingElements = elements.Where(elementIsInteresting);
        foreach (var e in interestingElements)
        {
            Console.WriteLine(e.Description);
        }
        
        var countExpensiveComputations = 0;
        Action incCount = () => countExpensiveComputations++;
        elements
            .Where(
                CreateSearchPredicate(
                    "car",
                    (Element e) => ExpensivelyComputed(
                        e, incCount
                    )
                ).Compile()
            )
            .Count();
                   
        Console.WriteLine("Property extractor is called {0} times.", countExpensiveComputations);
    }
    
    private class Element
    {
        public string Description { get; set; }
    }
    
    private static string ExpensivelyComputed(Element source, Action count)
    {
        count();
        return source.Description;
    }
    
    private static Expression<Func<T, bool>> CreateSearchPredicate<T>(
            string searchTerm,
            Expression<Func<T, string>> whereToSearch)
    {
        var extracted = Expression.Parameter(typeof(string), "extracted");
    
        Expression<Func<string, bool>> coalesceNullCheckWithSearch =
            Expression.Lambda<Func<string, bool>>(
                Expression.AndAlso(
                    Expression.Not(
                        Expression.Call(typeof(string), "IsNullOrEmpty", null, extracted)
                    ),
                    Expression.Call(extracted, "Contains", null, Expression.Constant(searchTerm))
                ),
                extracted);
                
        var elementParameter = Expression.Parameter(typeof(T), "element");
                
        return Expression.Lambda<Func<T, bool>>(
            Expression.Invoke(
                coalesceNullCheckWithSearch,
                Expression.Invoke(whereToSearch, elementParameter)
            ),
            elementParameter
        );
    }
}

Ausgabe

element => Invoke(extracted => (Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car")), Invoke(e => e.Description, element))
car
cargo
Madagascar
Predicate is called 5 times.

Das erste, was zu beachten ist, ist, wie die eigentliche Eigenschaft, in eine Invoke gehüllt, zugreift:

Invoke(e => e.Description, element)

, und dies ist der einzige Teil, der e.Description berührt, und anstelle dessen wird der extracted Parameter vom Typ string an den nächsten übergeben:

(Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car"))

Eine weitere wichtige Sache, die Sie hier beachten sollten, ist AndAlso . Es berechnet nur den linken Teil, wenn der erste Teil 'false' zurückgibt. Es ist ein häufiger Fehler, den bitweisen Operator 'And' anstelle von diesem zu verwenden, der immer beide Teile berechnet und in diesem Beispiel mit einer NullReferenceException fehlschlägt.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow