Philip Hendry's Blog

November 15, 2010

Creating a EqualityComparer on the fly with Lambdas

Filed under: .Net, Testing — philiphendry @ 11:12 am

When I’m writing unit tests I need the code to be a succinct as possible and one of the issues I’ve had is comparing objects sets/graphs to confirm expectations. Here’s a couple of the unit tests that test my solution and hopefully demonstrate what I’m after :

public class TestObject
{
   public string FirstProperty { get; set; }
   public string SecondProperty { get; set; }
}

[TestMethod]
public void EqualityCompareFactory_Should_DetectEquality_Given_TwoObjectInstancesWithTheSamePropertyValues()
{
  var comparer = EqualityComparerFactory<TestObject>.Create((x, y) => x.FirstProperty == y.FirstProperty);
  Assert.IsTrue(comparer.Equals(new TestObject { FirstProperty = "first" }, new TestObject { FirstProperty = "first"}));
}

[TestMethod]
public void EqualityCompareFactory_Should_DetectInequality_Given_TwoObjectInstancesWithTheDifferentPropertyValues()
{
  var comparer = EqualityComparerFactory<TestObject>.Create((x, y) => x.FirstProperty == y.FirstProperty);
  Assert.IsFalse(comparer.Equals(new TestObject { FirstProperty = "first" }, new TestObject { FirstProperty = "different" }));
}

The key here is that, rather than having to create a derived class of EqualityComparer<> I can simply use a factory and pass it a lambda that defines the comparison operation. In my business rule testing I can now write something like the following assertion :

var testResults = TimesheetItemBL.TestReturnSavePaymentFlags.ToList();
var expected = new List<TimesheetItemPaymentFlags>{
     new TimesheetItemPaymentFlags { AppliedRule = null, Date = testWeekStartDate, IsPaid = null },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(1), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(2), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(3), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(4), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(5), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.Paid, Date = testWeekStartDate.AddDays(6), IsPaid = true },
 };
NUnit.Framework.Assert.That(testResults, new CollectionEquivalentConstraint(expected).Using<TimesheetItemPaymentFlags>(
     EqualityComparerFactory<TimesheetItemPaymentFlags>.Create((x, y) => x.AppliedRule == y.AppliedRule && x.Date.Equals(y.Date) && x.IsPaid == y.IsPaid, x => x.Date.GetHashCode())
 ));

Admittedly this is still pretty verbose and I might extract some helper functions to reduce the size of this but it does demonstrate the simplicity of the solution which, by the way, looks like this :

public class DynamicEqualityComparer<T> : EqualityComparer<T>
{
   private readonly Func<T, T, bool> _equalsFunction;
   private readonly Func<T, int> _hashCodeFunction;

   internal DynamicEqualityComparer(Func<T, T, bool> equalsFunction, Func<T, int> hashCodeFunction)
   {
       _equalsFunction = equalsFunction;
       _hashCodeFunction = hashCodeFunction;
   }

   public override bool Equals(T x, T y)
   {
       return _equalsFunction(x, y);
   }

   public override int GetHashCode(T obj)
   {
       return _hashCodeFunction(obj);
   }
}

public class EqualityComparerFactory<T> where T : class
{
   public static DynamicEqualityComparer<T> Create(Func<T, T, bool> equalsFunction)
   {
       return new DynamicEqualityComparer<T>(equalsFunction, x => x.GetHashCode());
   }

   public static DynamicEqualityComparer<T> Create(Func<T, T, bool> equalsFunction, Func<T, int> hashCodeFunction)
   {
       return new DynamicEqualityComparer<T>(equalsFunction, hashCodeFunction);
   }
}

There’s some complexity involved in providing a hasCode function that is meaningful to the comparison, but otherwise it’s fairly basic.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Theme: Shocking Blue Green. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.