with son query que metodo for expresiones español c# lambda

c# - son - La manera más eficiente de probar la igualdad de expresiones lambda



linq with lambda expression in c# (4)

Dada una firma de método:

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

¿Cuál sería la forma más eficiente de decir si las dos expresiones son iguales? Esto solo tiene que funcionar para expresiones simples, con esto quiero decir que todo lo que sería "compatible" sería simple MemberExpressions, por ejemplo c => c.ID.

Una llamada de ejemplo podría ser:

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


Hmmm ... supongo que tendrías que analizar el árbol, verificar el tipo de nodo y miembro de cada uno. Voy a dar un ejemplo ...

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()); } } }


Sé que esta es una vieja pregunta, pero rodé mi propio comparador de igualdad de árbol de expresiones - https://github.com/yesmarket/yesmarket.Linq.Expressions

La implementación hace un uso intensivo de la clase ExpressionVisitor para determinar si dos árboles de expresiones son iguales. A medida que se atraviesan los nodos en el árbol de expresión, se comparan los nodos individuales para la igualdad.


Una solución canónica sería genial. Mientras tanto, creé una IEqualityComparer<Expression> . Esto es más bien una implementación detallada, así que creé una esencia para ello .

Se pretende que sea un comparador de sintaxis de árbol abstracto comprensivo. Para ello, compara cada tipo de expresión, incluidas las expresiones que aún no son compatibles con C # como Try and Switch y Block . Los únicos tipos que no se pueden comparar son Goto , Label , Loop y DebugInfo debido a mi limitado conocimiento de ellos.

Puede especificar si se deben comparar los nombres de parámetros y lambdas, y cómo manejar ConstantExpression .

Realiza un seguimiento posicional de los parámetros por contexto. Lambdas dentro de lambdas y parámetros variables de bloque catch son compatibles.


ACTUALIZACIÓN: Debido al interés de mi solución, he actualizado el código para que admita matrices, nuevos operadores y otras cosas, y compara los AST de una manera más elegante.

Aquí hay una versión mejorada del código de Marc y ahora está disponible como paquete nuget :

public static class LambdaCompare { public static bool Eq<TSource, TValue>( Expression<Func<TSource, TValue>> x, Expression<Func<TSource, TValue>> y) { return ExpressionsEqual(x, y, null, null); } public static bool Eq<TSource1, TSource2, TValue>( Expression<Func<TSource1, TSource2, TValue>> x, Expression<Func<TSource1, TSource2, TValue>> y) { return ExpressionsEqual(x, y, null, null); } public static Expression<Func<Expression<Func<TSource, TValue>>, bool>> Eq<TSource, TValue>(Expression<Func<TSource, TValue>> y) { return x => ExpressionsEqual(x, y, null, null); } private static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression rootX, LambdaExpression rootY) { if (ReferenceEquals(x, y)) return true; if (x == null || y == null) return false; var valueX = TryCalculateConstant(x); var valueY = TryCalculateConstant(y); if (valueX.IsDefined && valueY.IsDefined) return ValuesEqual(valueX.Value, valueY.Value); if (x.NodeType != y.NodeType || x.Type != y.Type) { if (IsAnonymousType(x.Type) && IsAnonymousType(y.Type)) throw new NotImplementedException("Comparison of Anonymous Types is not supported"); 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) && ExpressionsEqual(lx.Body, ly.Body, lx, ly); } if (x is MemberExpression) { var mex = (MemberExpression)x; var mey = (MemberExpression)y; return Equals(mex.Member, mey.Member) && ExpressionsEqual(mex.Expression, mey.Expression, rootX, rootY); } if (x is BinaryExpression) { var bx = (BinaryExpression)x; var by = (BinaryExpression)y; return bx.Method == @by.Method && ExpressionsEqual(bx.Left, @by.Left, rootX, rootY) && ExpressionsEqual(bx.Right, @by.Right, rootX, rootY); } if (x is UnaryExpression) { var ux = (UnaryExpression)x; var uy = (UnaryExpression)y; return ux.Method == uy.Method && ExpressionsEqual(ux.Operand, uy.Operand, 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 && ExpressionsEqual(cx.Object, cy.Object, rootX, rootY) && CollectionsEqual(cx.Arguments, cy.Arguments, rootX, rootY); } if (x is MemberInitExpression) { var mix = (MemberInitExpression)x; var miy = (MemberInitExpression)y; return ExpressionsEqual(mix.NewExpression, miy.NewExpression, rootX, rootY) && MemberInitsEqual(mix.Bindings, miy.Bindings, rootX, rootY); } if (x is NewArrayExpression) { var nx = (NewArrayExpression)x; var ny = (NewArrayExpression)y; return CollectionsEqual(nx.Expressions, ny.Expressions, rootX, rootY); } if (x is NewExpression) { var nx = (NewExpression)x; var ny = (NewExpression)y; return Equals(nx.Constructor, ny.Constructor) && CollectionsEqual(nx.Arguments, ny.Arguments, rootX, rootY) && (nx.Members == null && ny.Members == null || nx.Members != null && ny.Members != null && CollectionsEqual(nx.Members, ny.Members)); } if (x is ConditionalExpression) { var cx = (ConditionalExpression)x; var cy = (ConditionalExpression)y; return ExpressionsEqual(cx.Test, cy.Test, rootX, rootY) && ExpressionsEqual(cx.IfFalse, cy.IfFalse, rootX, rootY) && ExpressionsEqual(cx.IfTrue, cy.IfTrue, rootX, rootY); } throw new NotImplementedException(x.ToString()); } private static Boolean IsAnonymousType(Type type) { Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any(); Boolean nameContainsAnonymousType = type.FullName.Contains("AnonymousType"); Boolean isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType; return isAnonymousType; } private static bool MemberInitsEqual(ICollection<MemberBinding> bx, ICollection<MemberBinding> by, LambdaExpression rootX, LambdaExpression rootY) { if (bx.Count != by.Count) { return false; } if (bx.Concat(by).Any(b => b.BindingType != MemberBindingType.Assignment)) throw new NotImplementedException("Only MemberBindingType.Assignment is supported"); return bx.Cast<MemberAssignment>().OrderBy(b => b.Member.Name).Select((b, i) => new { Expr = b.Expression, b.Member, Index = i }) .Join( by.Cast<MemberAssignment>().OrderBy(b => b.Member.Name).Select((b, i) => new { Expr = b.Expression, b.Member, Index = i }), o => o.Index, o => o.Index, (xe, ye) => new { XExpr = xe.Expr, XMember = xe.Member, YExpr = ye.Expr, YMember = ye.Member }) .All(o => Equals(o.XMember, o.YMember) && ExpressionsEqual(o.XExpr, o.YExpr, rootX, rootY)); } private static bool ValuesEqual(object x, object y) { if (ReferenceEquals(x, y)) return true; if (x is ICollection && y is ICollection) return CollectionsEqual((ICollection)x, (ICollection)y); return Equals(x, y); } private static ConstantValue TryCalculateConstant(Expression e) { if (e is ConstantExpression) return new ConstantValue(true, ((ConstantExpression)e).Value); if (e is MemberExpression) { var me = (MemberExpression)e; var parentValue = TryCalculateConstant(me.Expression); if (parentValue.IsDefined) { var result = me.Member is FieldInfo ? ((FieldInfo)me.Member).GetValue(parentValue.Value) : ((PropertyInfo)me.Member).GetValue(parentValue.Value); return new ConstantValue(true, result); } } if (e is NewArrayExpression) { var ae = ((NewArrayExpression)e); var result = ae.Expressions.Select(TryCalculateConstant); if (result.All(i => i.IsDefined)) return new ConstantValue(true, result.Select(i => i.Value).ToArray()); } if (e is ConditionalExpression) { var ce = (ConditionalExpression)e; var evaluatedTest = TryCalculateConstant(ce.Test); if (evaluatedTest.IsDefined) { return TryCalculateConstant(Equals(evaluatedTest.Value, true) ? ce.IfTrue : ce.IfFalse); } } return default(ConstantValue); } 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 => ExpressionsEqual(o.X, o.Y, rootX, rootY)); } private static bool CollectionsEqual(ICollection x, ICollection y) { return x.Count == y.Count && x.Cast<object>().Select((e, i) => new { Expr = e, Index = i }) .Join(y.Cast<object>().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 => Equals(o.X, o.Y)); } private struct ConstantValue { public ConstantValue(bool isDefined, object value) : this() { IsDefined = isDefined; Value = value; } public bool IsDefined { get; private set; } public object Value { get; private set; } } }

Tenga en cuenta que no compara AST completo. En cambio, colapsa expresiones constantes y compara sus valores en lugar de su AST. Es útil para la validación de simulaciones cuando la lambda tiene una referencia a la variable local. En este caso, la variable se compara por su valor.

Pruebas unitarias:

[TestClass] public class Tests { [TestMethod] public void BasicConst() { var f1 = GetBasicExpr1(); var f2 = GetBasicExpr2(); Assert.IsTrue(LambdaCompare.Eq(f1, f2)); } [TestMethod] public void PropAndMethodCall() { var f1 = GetPropAndMethodExpr1(); var f2 = GetPropAndMethodExpr2(); Assert.IsTrue(LambdaCompare.Eq(f1, f2)); } [TestMethod] public void MemberInitWithConditional() { var f1 = GetMemberInitExpr1(); var f2 = GetMemberInitExpr2(); Assert.IsTrue(LambdaCompare.Eq(f1, f2)); } [TestMethod] public void AnonymousType() { var f1 = GetAnonymousExpr1(); var f2 = GetAnonymousExpr2(); Assert.Inconclusive("Anonymous Types are not supported"); } private static Expression<Func<int, string, string>> GetBasicExpr2() { var const2 = "some const value"; var const3 = "{0}{1}{2}{3}"; return (i, s) => string.Format(const3, (i + 25).ToString(CultureInfo.InvariantCulture), i + s, const2.ToUpper(), 25); } private static Expression<Func<int, string, string>> GetBasicExpr1() { var const1 = 25; return (first, second) => string.Format("{0}{1}{2}{3}", (first + const1).ToString(CultureInfo.InvariantCulture), first + second, "some const value".ToUpper(), const1); } private static Expression<Func<Uri, bool>> GetPropAndMethodExpr2() { return u => Uri.IsWellFormedUriString(u.ToString(), UriKind.Absolute); } private static Expression<Func<Uri, bool>> GetPropAndMethodExpr1() { return arg1 => Uri.IsWellFormedUriString(arg1.ToString(), UriKind.Absolute); } private static Expression<Func<Uri, UriBuilder>> GetMemberInitExpr2() { var isSecure = true; return u => new UriBuilder(u) { Host = string.IsNullOrEmpty(u.Host) ? "abc" : "def" , Port = isSecure ? 443 : 80 }; } private static Expression<Func<Uri, UriBuilder>> GetMemberInitExpr1() { var port = 443; return x => new UriBuilder(x) { Port = port, Host = string.IsNullOrEmpty(x.Host) ? "abc" : "def" }; } private static Expression<Func<Uri, object>> GetAnonymousExpr2() { return u => new { u.Host , Port = 443, Addr = u.AbsolutePath }; } private static Expression<Func<Uri, object>> GetAnonymousExpr1() { return x => new { Port = 443, x.Host, Addr = x.AbsolutePath }; } }