From e914b3b1df0c9ddab0a28e55ee224c5c4d594c37 Mon Sep 17 00:00:00 2001 From: afcabria <78049725+afcabria@users.noreply.github.com> Date: Tue, 23 May 2023 11:07:51 +0200 Subject: [PATCH 1/2] Add List & Dictionary initialization support There were no support for comparing expressions containing List or Dictionary initialization. Instead, NotImplementedException was thrown. --- Neleus.LambdaCompare/Comparer.cs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Neleus.LambdaCompare/Comparer.cs b/Neleus.LambdaCompare/Comparer.cs index 7e06a4f..66d48ae 100644 --- a/Neleus.LambdaCompare/Comparer.cs +++ b/Neleus.LambdaCompare/Comparer.cs @@ -99,6 +99,13 @@ public static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression && ExpressionsEqual(cx.IfFalse, cy.IfFalse, rootX, rootY) && ExpressionsEqual(cx.IfTrue, cy.IfTrue, rootX, rootY); } + if (x is ListInitExpression) + { + var lix = (ListInitExpression)x; + var liy = (ListInitExpression)y; + return ExpressionsEqual(lix.NewExpression, liy.NewExpression, rootX, rootY) + && ListInitsEqual(lix.Initializers, liy.Initializers, rootX, rootY); + } throw new NotImplementedException(x.ToString()); } @@ -130,6 +137,26 @@ private static bool MemberInitsEqual(ICollection bx, ICollection< .All(o => Equals(o.XMember, o.YMember) && ExpressionsEqual(o.XExpr, o.YExpr, rootX, rootY)); } + /// + /// Based on https://stackoverflow.com/questions/26680849/comparing-multivariable-boolean-functions + /// + private static bool ListInitsEqual(ICollection bx, ICollection by, LambdaExpression rootX, LambdaExpression rootY) + { + if (bx.Count != by.Count) + { + return false; + } + + return + bx.OrderBy(b => b.Arguments.FirstOrDefault()?.ToString()).Select((b, i) => new { b.AddMethod, b.Arguments, Index = i }) + .Join( + by.OrderBy(b => b.Arguments.FirstOrDefault()?.ToString()).Select((b, i) => new { b.AddMethod, b.Arguments, Index = i }), + o => o.Index, o => o.Index, (xe, ye) => new { X = xe, XAddMethod = xe.AddMethod, XArgs = xe.Arguments, Y = ye, YAddMethod = ye.AddMethod, YArgs = ye.Arguments }) + .All(o => + Equals(o.X, o.Y) || + Equals(o.XAddMethod, o.YAddMethod) && o.XArgs.Count == o.YArgs.Count && o.XArgs.Select((_, i) => ExpressionsEqual(o.XArgs[i], o.YArgs[i], rootX, rootY)).All(r => r)); + } + private static bool ValuesEqual(object x, object y) { if (ReferenceEquals(x, y)) @@ -209,4 +236,4 @@ public ConstantValue(bool isDefined, object value) public object Value { get; } } } -} \ No newline at end of file +} From 2728338b40f2dbb41d6ca3f2572f40a2d032d93d Mon Sep 17 00:00:00 2001 From: afcabria <78049725+afcabria@users.noreply.github.com> Date: Tue, 23 May 2023 11:18:00 +0200 Subject: [PATCH 2/2] Add List & Dictionary initialization support There were no support for comparing expressions containing List or Dictionary initialization. Instead, NotImplementedException was thrown. --- Neleus.LambdaCompare.Tests/Tests.cs | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/Neleus.LambdaCompare.Tests/Tests.cs b/Neleus.LambdaCompare.Tests/Tests.cs index dc85558..02433b4 100644 --- a/Neleus.LambdaCompare.Tests/Tests.cs +++ b/Neleus.LambdaCompare.Tests/Tests.cs @@ -59,5 +59,112 @@ public void AnonymousType() var f2 = (Expression>) (u => new { u.Host, Port = 443, Addr = u.AbsolutePath }); Assert.Inconclusive("Anonymous Types are not supported"); } + + private class ClassForTesting + { + public object Property1 { get; set; } + public object Property2 { get; set; } + public Dictionary DictProperty { get; set; } + } + + private class AdditionalClassForTesting + { + public DateTime? Date { get; set; } + } + + [TestMethod] + public void X() + { + // Object initialization + Expression, ClassForTesting>> ox = x => new ClassForTesting + { + Property1 = x.Sum(d => 1), + Property2 = x.Sum(d => 0) + }; + Expression, ClassForTesting>> oy = x => new ClassForTesting + { + Property1 = x.Sum(d => 1), + Property2 = x.Sum(d => 0) + }; + Assert.IsTrue(Lambda.Eq(ox, oy)); + + oy = x => new ClassForTesting + { + Property2 = x.Sum(d => 0), + Property1 = x.Sum(d => 1) + }; + Assert.IsTrue(Lambda.Eq(ox, oy)); + + ox = x => new ClassForTesting(); + oy = x => new ClassForTesting(); + Assert.IsTrue(Lambda.Eq(ox, oy)); + + + // List initialization + Expression>> lx = g => new List { 3, 7, 30 }; + Expression>> ly = g => new List { 3, 7, 30 }; + Assert.IsTrue(Lambda.Eq(lx, ly)); + + ly = g => new List { 7, 3, 30 }; + Assert.IsTrue(Lambda.Eq(lx, ly)); + + lx = g => new List(); + ly = g => new List(); + Assert.IsTrue(Lambda.Eq(lx, ly)); + + + // Dictionary initialization + Expression>> dx = x => new Dictionary { { 3, 33 }, { 7, 77 }, { 30, 333 } }; + Expression>> dy = x => new Dictionary { { 3, 33 }, { 7, 77 }, { 30, 333 } }; + Assert.IsTrue(Lambda.Eq(dx, dy)); + + dy = g => new Dictionary { { 7, 77 }, { 3, 33 }, { 30, 333 } }; + Assert.IsTrue(Lambda.Eq(dx, dy)); + + dx = g => new Dictionary(); + dy = g => new Dictionary(); + Assert.IsTrue(Lambda.Eq(dx, dy)); + + + // Dictionary initialization inside grouping + var utcNow = DateTime.UtcNow; + var limitDate3 = utcNow.AddDays(-3); + var limitDate7 = utcNow.AddDays(-7); + var limitDate30 = utcNow.AddDays(-30); + Expression, ClassForTesting>> gx = g => new ClassForTesting + { + DictProperty = new Dictionary + { + { 3, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate3 ? 1 : 0) }, + { 7, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate7 ? 1 : 0) }, + { 30, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate30 ? 1 : 0) } + } + }; + Expression, ClassForTesting>> gy = g => new ClassForTesting + { + DictProperty = new Dictionary + { + { 3, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate3 ? 1 : 0) }, + { 7, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate7 ? 1 : 0) }, + { 30, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate30 ? 1 : 0) } + } + }; + Assert.IsTrue(Lambda.Eq(gx, gy)); + + gy = g => new ClassForTesting + { + DictProperty = new Dictionary + { + { 7, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate7 ? 1 : 0) }, + { 3, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate3 ? 1 : 0) }, + { 30, g.Sum(d => d.Date.HasValue && d.Date.Value < limitDate30 ? 1 : 0) } + } + }; + Assert.IsTrue(Lambda.Eq(gx, gy)); + + gx = g => new ClassForTesting { DictProperty = new Dictionary() }; + gy = g => new ClassForTesting { DictProperty = new Dictionary() }; + Assert.IsTrue(Lambda.Eq(gx, gy)); + } } }