我正在编写的应用程序涉及将明文解析为布尔lambda表达式。 出于测试目的,我需要确保正确进行此解析。为此,我编写了一个递归表达式树比较例程bool ExpressionComparison.EquvalentTo(this Expression self, Expression other)
。如果self
和other
都是逻辑运算符,并且它们共享同一组操作数,则EquvalentTo
应返回true。
我当前的算法使用ExpressionVisitor
。我将lambda表达式的主体传递给此访问者,它用布尔参数替换所有操作数(非逻辑运算符的表达式)。我在两个lambdas上调用它,比较操作数,重新排序第二个lambda的二进制参数,如果需要,所以对应于相同的操作数,编译两个二进制函数,并详尽地比较结果。
有没有更有效的方法来比较两个布尔函数的逻辑等价?
public static class ExpressionComparison
{
public static bool Equals(this Expression left, Expression right)
{
return Compare(left, right, Equals);
}
public static bool EquvalentTo(this Expression self, Expression other)
{
if (Object.Equals(self, other))
return true;
if (self == null || other == null)
return false;
if ((self.NodeType == ExpressionType.Not || self.NodeType == ExpressionType.AndAlso || self.NodeType == ExpressionType.OrElse) &&
(other.NodeType == ExpressionType.Not || other.NodeType == ExpressionType.AndAlso || other.NodeType == ExpressionType.OrElse))
return new Visitor(self).Compare(new Visitor(other), EquvalentTo);
else if (self.NodeType != other.NodeType)
return false;
if (self.NodeType == ExpressionType.Constant)
{
var _self = (ConstantExpression)self;
var _other = (ConstantExpression)other;
if (Object.ReferenceEquals(_self.Value, _other.Value))
return true;
}
if (self.Type != other.Type)
return false;
switch (self.NodeType)
{
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.ExclusiveOr:
var selfsubs = ((BinaryExpression)self).Linearize().ToList();
var othersubs = ((BinaryExpression)other).Linearize().ToList();
foreach (var leftsub in selfsubs)
{
int i;
for (i = othersubs.Count - 1; i >= 0; i--)
if (EquvalentTo(leftsub, othersubs[i]))
{
othersubs.RemoveAt(i);
break;
}
if (i < 0)
return false;
}
return othersubs.Count == 0;
}
return Compare(self, other, EquvalentTo);
}
private static bool Compare(Expression self, Expression other, Func<Expression, Expression, bool> comparer)
{
if (Object.Equals(self, other))
return true;
if (self == null || other == null)
return false;
if (self.NodeType != other.NodeType)
return false;
if (self.Type != other.Type)
return false;
if (self is BinaryExpression)
{
var _left = (BinaryExpression)self;
var _right = (BinaryExpression)other;
return comparer(_left.Left, _right.Left) && comparer(_left.Right, _right.Right);
}
else if (self is ConditionalExpression)
{
var _left = (ConditionalExpression)self;
var _right = (ConditionalExpression)other;
return comparer(_left.IfFalse, _right.IfFalse) && comparer(_left.IfTrue, _right.IfTrue) && comparer(_left.Test, _right.Test);
}
else if (self is ConstantExpression)
{
var _left = (ConstantExpression)self;
var _right = (ConstantExpression)other;
if (!Object.Equals(_left.Value, _right.Value))
return false;
return true;
}
else if (self is DefaultExpression)
{
return true;
}
else if (self is IndexExpression)
{
var _left = (IndexExpression)self;
var _right = (IndexExpression)other;
if (_left.Arguments.Count != _right.Arguments.Count)
return false;
if (!Object.Equals(_left.Object, _right.Object))
return false;
if (!Object.Equals(_left.Indexer, _right.Indexer))
return false;
for (var i = _left.Arguments.Count - 1; i >= 0; i--)
if (!comparer(_left.Arguments[i], _right.Arguments[i]))
return false;
return true;
}
else if (self is InvocationExpression)
{
var _left = (InvocationExpression)self;
var _right = (InvocationExpression)other;
if (_left.Arguments.Count != _right.Arguments.Count)
return false;
if (!comparer(_left.Expression, _right.Expression))
return false;
for (var i = _left.Arguments.Count - 1; i >= 0; i--)
if (!comparer(_left.Arguments[i], _right.Arguments[i]))
return false;
return true;
}
else if (self is LambdaExpression)
{
var _left = (LambdaExpression)self;
var _right = (LambdaExpression)other;
if (_left.Parameters.Count != _right.Parameters.Count)
return false;
for (var i = _left.Parameters.Count - 1; i >= 0; i--)
if (!comparer(_left.Parameters[i], _right.Parameters[i]))
return false;
if (!Object.Equals(_left.ReturnType, _right.ReturnType))
return false;
return comparer(_left.Body, _right.Body);
}
else if (self is ListInitExpression)
{
var _left = (ListInitExpression)self;
var _right = (ListInitExpression)other;
if (comparer(_left.NewExpression, _right.NewExpression))
return false;
for (var i = _left.Initializers.Count - 1; i >= 0; i--)
{
var leftinit = _left.Initializers[i];
var rightinit = _right.Initializers[i];
if (Object.Equals(leftinit, rightinit))
continue;
if (!Object.Equals(leftinit.AddMethod, rightinit.AddMethod))
return false;
if (leftinit.Arguments.Count != rightinit.Arguments.Count)
return false;
for (var j = leftinit.Arguments.Count - 1; j >= 0; j--)
if (!leftinit.Arguments[i].EquvalentTo(rightinit.Arguments[i]))
return false;
}
return true;
}
else if (self is MemberExpression)
{
var _left = (MemberExpression)self;
var _right = (MemberExpression)other;
if (!Object.Equals(_left.Member, _right.Member))
return false;
return comparer(_left.Expression, _right.Expression);
}
else if (self is MemberInitExpression)
{
}
else if (self is MethodCallExpression)
{
var _left = (MethodCallExpression)self;
var _right = (MethodCallExpression)other;
if (!Object.Equals(_left.Method, _right.Method))
return false;
if (!comparer(_left.Object, _right.Object))
return false;
for (var i = _left.Arguments.Count - 1; i >= 0; i--)
if (!comparer(_left.Arguments[i], _right.Arguments[i]))
return false;
return true;
}
else if (self is NewArrayExpression)
{
var _left = (NewArrayExpression)self;
var _right = (NewArrayExpression)other;
for (var i = _left.Expressions.Count - 1; i >= 0; i--)
if (!comparer(_left.Expressions[i], _right.Expressions[i]))
return false;
return true;
}
else if (self is NewExpression)
{
var _left = (NewExpression)self;
var _right = (NewExpression)other;
if (!Object.Equals(_left.Constructor, _right.Constructor))
return false;
for (var i = _left.Members.Count - 1; i >= 0; i--)
if (!Object.Equals(_left.Members[i], _right.Members[i]))
return false;
for (var i = _left.Arguments.Count - 1; i >= 0; i--)
if (!comparer(_left.Arguments[i], _right.Arguments[i]))
return false;
return true;
}
else if (self is ParameterExpression)
{
return true;
}
else if (self is TypeBinaryExpression)
{
var _left = (TypeBinaryExpression)self;
var _right = (TypeBinaryExpression)other;
if (_left.TypeOperand != _right.TypeOperand)
return false;
return comparer(_left.Expression, _right.Expression);
}
else if (self is UnaryExpression)
{
var _left = (UnaryExpression)self;
var _right = (UnaryExpression)other;
if (!Object.Equals(_left.Method, _right.Method))
return false;
return comparer(_left.Operand, _right.Operand);
}
throw new Exception("Unhandled expression type: " + self.GetType());
}
private static IEnumerable<Expression> Linearize(this BinaryExpression expr)
{
var type = expr.NodeType;
if (expr.Left.NodeType == type)
foreach (Expression subexpr in ((BinaryExpression)expr.Left).Linearize())
yield return subexpr;
else
yield return expr.Left;
if (expr.Right.NodeType == type)
foreach (Expression subexpr in ((BinaryExpression)expr.Right).Linearize())
yield return subexpr;
else
yield return expr.Right;
}
class Visitor : ExpressionVisitor
{
IList<Expression> _expressions = new List<Expression>();
IList<ParameterExpression> _parameters = new List<ParameterExpression>();
ParameterExpression _next(Expression expression)
{
var parameter = Expression.Parameter(typeof(Boolean));
_expressions.Add(expression);
_parameters.Add(parameter);
return parameter;
}
public override Expression Visit(Expression node)
{
switch (node.NodeType)
{
case ExpressionType.Not:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
return base.Visit(node);
default:
return _next(node);
}
}
public Expression Visited { get; private set; }
public Expression[] Expressions { get; private set; }
public ParameterExpression[] Parameters { get; private set; }
public Visitor(Expression node)
{
Visited = Visit(node);
Expressions = _expressions.ToArray();
Parameters = _parameters.ToArray();
}
private Delegate NaturalOrder()
{
return Expression.Lambda(Visited, Parameters).Compile();
}
private Delegate ForcedOrder(int[] order)
{
return Expression.Lambda(Visited, order.Select(i => Parameters[i])).Compile();
}
public bool Compare(Visitor that, Func<Expression, Expression, bool> comparer)
{
if (this.Expressions.Length != that.Expressions.Length)
return false;
var order = new int[this.Expressions.Length];
var CompareExpr = that.Expressions.ToArray();
for (int i = Expressions.Length - 1, j; i >= 0; i--)
if (comparer(Expressions[i], CompareExpr[i]))
order[i] = i;
else
{
for (j = CompareExpr.Length - 1; j >= 0; j--)
if (i == j)
continue;
else if (comparer(Expressions[i], CompareExpr[j]))
{
order[i] = j;
CompareExpr[j] = null;
break;
}
if (j < 0)
return false;
}
dynamic thisd = this.NaturalOrder();
dynamic thatd = that.ForcedOrder(order);
var args = new bool[order.Length];
for (var i = ((ulong)1 << order.Length); i > 0; i--)
{
for (var j = (byte)order.Length - 1; j >= 0; j--)
args[j] = ((i - 1) & ((ulong)1 << j)) != 0;
if (!FuncHelper.Exec(thisd, thatd, args))
return false;
}
return true;
}
}
}
Asside / Update:我正在将我在LINQ的WHERE子句中使用的Expression<Func<TModel, bool>>
与实体进行比较。我想知道给定的文本字符串正确地解析为lambda,为此,我将生成的lambda与我期望的lambda进行比较。 我想知道两个lambda都会返回相同的结果集。我实际上无法对数据进行测试,因为有些查询是故意荒谬的,直到确保我的解析器是健壮的。此外,数据正在发生变化,查询A可能与查询B略有不同,但目前它们都返回相同的集合,等等。所以我测试了lambdas的等价性。
我将以下两种表达式定义为等效:
Object.Equals(a, b)
返回true a
和b
在逻辑上等效(仅适用于Not
,AndAlso
和OrElse
个节点;定义如下)a
和b
可交换等效(仅适用于Add
,Multiply
,(按位)And
,{ {1}}和Or
个节点;定义如下)ExclusiveOr
的{{1}}和ExpressionType
完全相同,所有操作数,运算符,参数,参数等等于(对于表达式)或等于(对于其他对象) )。我将两个表达式定义为逻辑等效,如果:它们的'operands'是等价的,如果我用布尔参数替换每个布尔操作数,那么两个结果的n-ary布尔函数具有相同的真值表。过程如下:
a
,b
或Not
的任何节点,用新的布尔参数替换其他节点。布尔参数和它们替换的表达式在数组中捕获。AndAlso
被认为等同于OrElse
,但在更复杂的情况下,不同的顺序参数可能导致不同的真值表。)paramA.PropA == 1 && paramB.PropB == 2
对应于某个表达式paramB.PropB == 2 && paramA.PropA == 1
,它本身等同于b.Parameters[i]
。 我将两个表达式定义为交换等效,如果: b.Expressions[j]
a.Expressions[i]
和ExpressionType
的{{1}}完全相等,对于a和b,我是向下走到树上,停在与a / b不同a
的任何b
上,并将该表达式放在列表中,Expression
列表和{{1}列表}包含同一组ExpressionType
(由等效性确定)。