.NET Framework
Деревья выражений
Поиск…
замечания
Деревья выражений - это структуры данных, используемые для представления выражений кода в .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
toSQL
для создания предложения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.