49 votes

Le moyen le plus efficace de tester l'égalité des expressions lambda

Étant donné une signature de méthode:

 public bool AreTheSame<T>(Expression<Func<T, object>> exp1, Expression<Func<T, object>> exp2)
 

Quelle serait la façon la plus efficace de dire si les deux expressions sont identiques? Cela ne doit fonctionner que pour des expressions simples, j'entends par là que tout ce qui serait "pris en charge" serait de simples MemberExpressions, par exemple c => c.ID.

Un exemple d'appel pourrait être:

 AreTheSame<User>(u1 => u1.ID, u2 => u2.ID); --> would return true
 

47voto

neleus Points 91

Voici une version améliorée du code de Marc:

 public static class LambdaCompare
{
    public static bool AreEqual<TSource, TValue>(
        Expression<Func<TSource, TValue>> x,
        Expression<Func<TSource, TValue>> y)
    {
        return ExpressionEqual(x, y, null, null);
    }

    private static bool ExpressionEqual(Expression x, Expression y, LambdaExpression rootX, LambdaExpression rootY)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        if (x.NodeType != y.NodeType
            || x.Type != y.Type) return false;

        if (x is LambdaExpression)
        {
            var lx = (LambdaExpression) x;
            var ly = (LambdaExpression) y;
            var paramsX = lx.Parameters;
            var paramsY = ly.Parameters;
            return CollectionsEqual(paramsX, paramsY, lx, ly) && ExpressionEqual(lx.Body, ly.Body, lx, ly);
        }
        if (x is MemberExpression)
        {
            var mex = (MemberExpression) x;
            var mey = (MemberExpression) y;
            return MemberExpressionsEqual(mex, mey, rootX, rootY);
        }
        if (x is BinaryExpression)
        {
            var bx = (BinaryExpression) x;
            var by = (BinaryExpression) y;
            return bx.Method == @by.Method && ExpressionEqual(bx.Left, @by.Left, rootX, rootY) &&
                   ExpressionEqual(bx.Right, @by.Right, rootX, rootY);
        }
        if (x is ParameterExpression)
        {
            var px = (ParameterExpression) x;
            var py = (ParameterExpression) y;
            return rootX.Parameters.IndexOf(px) == rootY.Parameters.IndexOf(py);
        }
        if (x is MethodCallExpression)
        {
            var cx = (MethodCallExpression)x;
            var cy = (MethodCallExpression)y;
            return cx.Method == cy.Method
                   && ExpressionEqual(cx.Object, cy.Object, rootX, rootY)
                   && CollectionsEqual(cx.Arguments, cy.Arguments, rootX, rootY);
        }
        if (x is ConstantExpression)
        {
            var cx = (ConstantExpression)x;
            var cy = (ConstantExpression)y;
            return Equals(cx.Value, cy.Value);
        }

        throw new NotImplementedException(x.ToString());
    }

    private static bool CollectionsEqual(IEnumerable<Expression> x, IEnumerable<Expression> y, LambdaExpression rootX, LambdaExpression rootY)
    {
        return x.Count() == y.Count()
               && x.Select((e, i) => new {Expr = e, Index = i})
                   .Join(y.Select((e, i) => new { Expr = e, Index = i }),
                         o => o.Index, o => o.Index, (xe, ye) => new { X = xe.Expr, Y = ye.Expr })
                   .All(o => ExpressionEqual(o.X, o.Y, rootX, rootY));
    }

    private static bool MemberExpressionsEqual(MemberExpression x, MemberExpression y, LambdaExpression rootX, LambdaExpression rootY)
    {
        if (x.Expression.NodeType != y.Expression.NodeType)
            return false;
        switch (x.Expression.NodeType)
        {
            case ExpressionType.Constant:
                var constx = GetValueOfConstantExpression(x);
                var consty = GetValueOfConstantExpression(y);
                return Equals(constx, consty);
            case ExpressionType.Parameter:
                return Equals(x.Member, y.Member) && ExpressionEqual(x.Expression, y.Expression, rootX, rootY);
            default:
                throw new NotImplementedException(x.ToString());
        }
    }

    private static object GetValueOfConstantExpression(MemberExpression mex)
    {
        var o = ((ConstantExpression) mex.Expression).Value;
        return mex.Member is FieldInfo
                          ? ((FieldInfo) mex.Member).GetValue(o)
                          : ((PropertyInfo) mex.Member).GetValue(o);
    }
}
 

Notez qu'il ne compare pas l'AST complet. Au lieu de cela, il réduit les expressions constantes et compare leurs valeurs plutôt que leur AST. Il est utile pour la validation des simulations lorsque le lambda a une référence à une variable locale. Dans son cas, la variable est comparée par sa valeur.

38voto

Marc Gravell Points 482669

Hmmm ... Je suppose que vous devrez analyser l'arbre, vérifier le type de nœud et le membre de chacun. Je vais vous donner un exemple ...

 using System;
using System.Linq.Expressions;
class Test {
    public string Foo { get; set; }
    public string Bar { get; set; }
    static void Main()
    {
        bool test1 = FuncTest<Test>.FuncEqual(x => x.Bar, y => y.Bar),
            test2 = FuncTest<Test>.FuncEqual(x => x.Foo, y => y.Bar);
    }

}
// this only exists to make it easier to call, i.e. so that I can use FuncTest<T> with
// generic-type-inference; if you use the doubly-generic method, you need to specify
// both arguments, which is a pain...
static class FuncTest<TSource>
{
    public static bool FuncEqual<TValue>(
        Expression<Func<TSource, TValue>> x,
        Expression<Func<TSource, TValue>> y)
    {
        return FuncTest.FuncEqual<TSource, TValue>(x, y);
    }
}
static class FuncTest {
    public static bool FuncEqual<TSource, TValue>(
        Expression<Func<TSource,TValue>> x,
        Expression<Func<TSource,TValue>> y)
    {
        return ExpressionEqual(x, y);
    }
    private static bool ExpressionEqual(Expression x, Expression y)
    {
        // deal with the simple cases first...
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        if (   x.NodeType != y.NodeType
            || x.Type != y.Type ) return false;

        switch (x.NodeType)
        {
            case ExpressionType.Lambda:
                return ExpressionEqual(((LambdaExpression)x).Body, ((LambdaExpression)y).Body);
            case ExpressionType.MemberAccess:
                MemberExpression mex = (MemberExpression)x, mey = (MemberExpression)y;
                return mex.Member == mey.Member; // should really test down-stream expression
            default:
                throw new NotImplementedException(x.NodeType.ToString());
        }
    }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X