Рефакторинг лямбда-выражений C #
-
10-07-2019 - |
Вопрос
У меня есть несколько Expression<Func<User,bool>>
выражения, которые совместно используют свойства.Например,
Expression<Func<User, bool>> e1 =
(User u) => u.IsActive && u.Group != "PROCESS" && u.Name != null;
Expression<Func<User, bool>> e2 =
(User u) => u.IsActive && u.Group != "PROCESS" && u.Name != "A";
Expression<Func<User, bool>> e3 =
(User u) => u.IsActive && u.Group != "PROCESS" && u.Name != "B";
Есть ли простой способ поместить u.IsActive && u.Group != "PROCESS"
в переменной и использовать ее в e1, e2 и e3?Отредактированный :И я все еще хочу то же самое дерево.
Кажется, я могу сделать это, построив выражение с помощью Expression.Lambda<Func<User, bool>>(BinaryExpression.AndAlso(
и т.д...Но вместо того, чтобы упростить мой код, это сделало его более трудным для чтения.
Решение
Я полагаю, что для вашего случая нет более чистого способа сделать это. Вы можете использовать BinaryExpression
, как вы упомянули. Вы можете заключить вызовы Expression.Lambda
и <=> в метод и вызвать его вместо этого (например, PredicateBuilder .A ), но ни один из них не является настолько чистым, как текущий синтаксис IMO.
Другие советы
Проблема с лямбда-выражениями заключается в том, что они неизменяемы, и вы не можете легко заменить параметры лямбда-выражения.Моя первоначальная идея состояла в том, чтобы сделать что-то вроде этого (к сожалению, из этого ничего не выйдет):
public static class ExpressionExtesions
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> baseCondition, Expression<Func<T, bool>> additionalCondition)
{
var and = Expression.AndAlso(baseCondition.Body, additionalCondition.Body);
return Expression.Lambda<Func<T, bool>>(and, baseCondition.Parameters); // additionalCondition.Body still uses its own parameters so this fails on Compile()
}
}
и использовать в коде:
Expression<Func<User, bool>> e = usr => usr.IsActive && usr.Group != "PROCESS";
var e1 = e.And(u => u.Name != null);
var e2 = e.And(u => u.Name != "A");
var e3 = e.And(u => u.Name != "B");
Возможное решение
Вы можете попробовать использовать один из проектов, направленных на реализацию конструкторов выражений.Я не использовал ни один из них, но Google выдает множество ссылок, например:
- Металлик
- LINQ:СОЗДАНИЕ IQUERYABLE PROVIDER - ЧАСТЬ II
- Работа с Неизменяемыми деревьями выражений Linq
Другой подход
Если вы используете эти выражения в LINQ для фильтрации значений, вы можете использовать другой подход (не комбинировать выражения, а комбинировать фильтры):
var activeUsers = allUsers.Where(usr => usr.IsActive && usr.Group != "PROCESS");
var usersAll = activeUsers.Where(u => u.Name != null);
var usersNotA = activeUsers.Where(u => u.Name != "A");
var usersNotB = activeUsers.Where(u => u.Name != "B");
Я не думаю, что всегда есть лучший ответ, чем тот, который вы уже используете. Как упоминает Мерад, вам нужно будет построить более глубокое дерево, используя BinaryExpression, и я думаю, что это будет шагом назад в удобочитаемости вашего текущего кода.
В зависимости от вашего использования, вы можете сохранить некоторые строки кода, используя семантику замыкания и делая что-то вроде этого:
string name = null;
Expression<Func<User, bool>> e =
(User u) => u.IsActive && u.Group != "PROCESS" && u.Name != name;
var expr = e.Compile();
name = "A";
var result = expr.Invoke(u); //True (assume u.Name = "B")
name = "B";
result = expr.Invoke(u); //False
... но будет ли это использоваться, будет зависеть от того, что вы делаете с скомпилированным делегатом. Может быть, совершенно бесполезен для вас, но стоит упомянуть на всякий случай!
var test = new Func<User, bool>(u=> u.IsActive && u.Group != "PROCESS");
Expression<Func<User, bool>> e1 = (User u) => test(u) && u.Name != null;
Expression<Func<User, bool>> e2 = (User u) => test(u) && u.Name != "A";
Expression<Func<User, bool>> e3 = (User u) => test(u) && u.Name != "B";