Ну, вы можете использовать Expression.AndAlso
/ OrElse
и т. д. для объединения логических выражений, но проблема заключается в параметрах; вы работаете с тем же ParameterExpression
в expr1 и expr2? Если это так, это проще:
var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
Это также хорошо работает, чтобы свести на нет одну операцию:
static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}
В противном случае, в зависимости от поставщика LINQ, вы можете скомбинируйте их с Invoke
:
// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
Где-то у меня есть код, который переписывает дерево выражений, заменяя узлы, чтобы удалить необходимость в Invoke
, но он довольно длинный (и Я не могу вспомнить, где я его оставил ...)
Обобщенная версия, которая выбирает самый простой маршрут:
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
Начиная с .net 4.0. Существует класс ExpressionVistor, который позволяет создавать выражения, безопасные для EF.
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
У меня есть пара улучшений для вашего подхода.
1) Вам необходимо добавить блок try-finally
, чтобы установить _beingUsed = false
гарантированно. В противном случае, если трудоемкий код вызовет исключение, ни один вызывающий не сможет запустить этот код.
2) Поскольку код ниже первой блокировки доступен только для одного абонента, я не вижу необходимости во второй блокировке.
Вот улучшенный код:
public ErrorCodes SomeFunction()
{
lock(_lock)
{
if(_beingUsed)
return ErrorCodes.BeingUsed;
_beingUsed = true;
}
// here can enter only one caller
try
{
// Time consuming code that is not thread safe.
return ErrorCodes.OK;
}
finally
{
_beingUsed = false;
}
}
Если производительность очень критична, вы можете рассмотреть подход без блокировки:
int _flag = 0;
public ErrorCodes SomeFunction()
{
if (Interlocked.Exchange(ref _flag, 1) == 0)
{
// here can enter only one caller
try
{
//process long operation
return ErrorCodes.OK;
}
finally
{
Interlocked.Exchange(ref _flag, 0);
}
}
else
{
//immediately return
return ErrorCodes.BeingUsed;
}
}
Но этот код выглядит более сложным и требует понимания программирования без блокировки.