Поиск…


замечания

Деревья выражений - это структуры данных, используемые для представления выражений кода в .NET Framework. Они могут быть сгенерированы с помощью кода и программно переведены, чтобы перевести код на другой язык или выполнить его. Самым популярным генератором деревьев выражений является сам компилятор C #. Компилятор C # может генерировать деревья выражений, если выражение lambda назначается переменной типа Expression <Func <... >>. Обычно это происходит в контексте LINQ. Наиболее популярным потребителем является поставщик LINQ от Entity Framework. Он потребляет деревья выражений, заданные Entity Framework, и генерирует эквивалентный код SQL, который затем выполняется в базе данных.

Простое дерево выражений, созданное компилятором C #

Рассмотрим следующий код C #

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

Поскольку компилятор C # видит, что выражение lambda назначается типу Expression, а не тип делегата, он генерирует дерево выражений, примерно эквивалентное этому коду

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

Корнем дерева является выражение лямбда, которое содержит тело и список параметров. Лямбда имеет 1 параметр под названием «a». Тело - это одно выражение выражения BinaryExpression CLR и NodeType для Add. Это выражение представляет собой дополнение. Он имеет два подвыражения, обозначаемых как «левый» и «правый». Слева - это Параметрическое выражение для параметра «a», а Right - константное выражение со значением 1.

Простейшим использованием этого выражения является его печать:

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

Что печатает эквивалентный код C #.

Дерево выражений можно скомпилировать в делегат C # и выполнить с помощью CLR

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

Обычно выражения переводятся на другие языки, такие как SQL, но могут также использоваться для вызова частных, защищенных и внутренних членов публичных или непубличных типов в качестве альтернативы Reflection.

построение предиката поля формы == значение

Чтобы создать выражение, подобное _ => _.Field == "VALUE" во время выполнения.

Учитывая предикат _ => _.Field и строковое значение "VALUE" , создайте выражение, которое проверяет, является ли предикат истинным.

Выражение подходит для:

  • IQueryable<T> , IEnumerable<T> для проверки предиката.
  • сущности или Linq to SQL для создания предложения Where , которое проверяет предикат.

Этот метод построит соответствующее выражение Equal которое проверяет, равно ли Field "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;
}

Предикат можно использовать, включив предикат в метод расширения Where .

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

Выражение для извлечения статического поля

Пример такого типа:

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

Мы можем получить значение StaticPublicField:

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

Затем он может быть скомпилирован в делегат для получения значения поля.

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

// результат fieldValue - StaticPublicFieldValue

Класс InvocationExpression

Класс InvocationExpression позволяет вызывать другие лямбда-выражения, которые являются частями одного и того же дерева выражений.

Вы создаете их со статическим методом Expression.Invoke .

Проблема Мы хотим получить предметы, которые имеют «автомобиль» в своем описании. Нам нужно проверить его на нуль, прежде чем искать строку внутри, но мы не хотим, чтобы ее вызывали чрезмерно, так как вычисление могло быть дорогостоящим.

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

Выход

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

Первое, что нужно отметить, - это то, как фактический доступ к собственности, завернутый в Invoke:

Invoke(e => e.Description, element)

, и это единственная часть, e.Description , а вместо нее extracted параметр string типа передается следующему:

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

Еще одно важное замечание - AndAlso . Он вычисляет только левую часть, если первая часть возвращает «false». Ошибочно использовать побитовый оператор «А» вместо него, который всегда вычисляет обе части, и в этом примере завершится с ошибкой NullReferenceException.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow