Szukaj…


Uwagi

Drzewa wyrażeń to struktury danych używane do reprezentowania wyrażeń kodu w .NET Framework. Można je generować za pomocą kodu i programowo przeglądać w celu przetłumaczenia kodu na inny język lub wykonania. Najpopularniejszym generatorem drzew wyrażeń jest sam kompilator C #. Kompilator C # może generować drzewa wyrażeń, jeśli wyrażenie lambda jest przypisane do zmiennej typu Expression <Func <... >>. Zwykle dzieje się tak w kontekście LINQ. Najpopularniejszym konsumentem jest dostawca LINQ Entity Framework. Zużywa drzewa wyrażeń podane w Entity Framework i generuje równoważny kod SQL, który jest następnie wykonywany dla bazy danych.

Proste drzewo wyrażeń generowane przez kompilator C #

Rozważ następujący kod C #

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

Ponieważ kompilator C # widzi, że wyrażenie lambda jest przypisane do typu wyrażenia, a nie do typu delegowanego, generuje drzewo wyrażeń w przybliżeniu równoważne z tym kodem

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

Korzeń drzewa to wyrażenie lambda, które zawiera ciało i listę parametrów. Sonda lambda ma 1 parametr o nazwie „a”. Treść jest pojedynczym wyrażeniem typu CLR BinaryExpression i NodeType of Add. To wyrażenie reprezentuje dodanie. Ma dwa podwyrażenia oznaczone jako Lewy i Prawy. Lewy to ParameterExpression dla parametru „a”, a prawy to ConstantExpression o wartości 1.

Najprostszym zastosowaniem tego wyrażenia jest wydrukowanie go:

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

Który wypisuje równoważny kod C #.

Drzewo wyrażeń można skompilować w delegata C # i wykonać przez CLR

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

Zazwyczaj wyrażenia są tłumaczone na inne języki, takie jak SQL, ale mogą być również używane do wywoływania prywatnych, chronionych i wewnętrznych członków typów publicznych lub niepublicznych jako alternatywy dla Reflection.

budowanie predykatu pola formularza == wartość

Aby zbudować wyrażenie takie jak _ => _.Field == "VALUE" w czasie wykonywania.

Biorąc pod uwagę predykat _ => _.Field i wartość ciągu "VALUE" , utwórz wyrażenie, które sprawdza, czy predykat jest prawdziwy.

Wyrażenie jest odpowiednie dla:

  • IQueryable<T> , IEnumerable<T> do testowania predykatu.
  • framework encji lub Linq do SQL aby utworzyć klauzulę Where , która testuje predykat.

Ta metoda zbuduje odpowiednie wyrażenie Equal które sprawdza, czy Field jest równe "VALUE" .

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

Predykat można wykorzystać, włączając predykat do metody rozszerzenia Where .

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

Wyrażenie do pobierania pola statycznego

Mając taki przykład:

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

Możemy pobrać wartość StaticPublicField:

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

Następnie można go skompilować w delegata w celu pobrania wartości pola.

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

// wynikiem fieldValue jest StaticPublicFieldValue

InvocationExpression Class

Klasa InvocationExpression umożliwia wywoływanie innych wyrażeń lambda, które są częścią tego samego drzewa wyrażeń.

Tworzysz je za pomocą statycznej metody Expression.Invoke .

Problem Chcemy dostać się do przedmiotów, które mają w opisie słowo „samochód”. Musimy sprawdzić, czy nie ma wartości null przed wyszukaniem ciągu wewnątrz, ale nie chcemy, aby był on wywoływany nadmiernie, ponieważ obliczenia mogą być kosztowne.

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

Wynik

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

Pierwszą rzeczą, na którą należy zwrócić uwagę, jest faktyczny dostęp do właściwości, zawinięty w Invoke:

Invoke(e => e.Description, element)

, i jest to jedyna część, która dotyka e.Description , a zamiast niego extracted parametr typu string jest przekazywany do następnej:

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

Inną ważną rzeczą, na którą warto tutaj zwrócić uwagę, jest AndAlso . Oblicza tylko lewą część, jeśli pierwsza część zwraca „fałsz”. Powszechnym błędem jest użycie zamiast tego operatora bitowego „I”, który zawsze oblicza obie części i kończy się niepowodzeniem z wyjątkiem NullReferenceException w tym przykładzie.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow