How to Use .Except with EqualityComparer

Today we’re going to cover an advanced scenario where we need to compare lists of a class we created. We’ll start with an implementation of EqualityComparer<T> which consists of overriding two methods: Equals and GetHashCode. What was a little tricky about this implementation is this subtle, but critical, comment in the code sample on MSDN (in the Enumerable.Except documentation):

// If Equals() returns true for a pair of objects,
// GetHashCode must return the same value for these objects.

// GetHashCode must return the same value for these objects.

public class WidgetComparer : EqualityComparer<Widget>
{
public override bool Equals(Widget x, Widget y)
{
if (ReferenceEquals(x, y)) return true;

return x.Id == y.Id && x.Name == y.Name && x.Price == y.Price;
}

public override int GetHashCode(Widget obj)
{
if (ReferenceEquals(obj, null)) return 0;

var nameHash = obj.Name == null ? 0 : obj.Name.GetHashCode();
var idHash = obj.Id.GetHashCode();
var priceHash = obj.Price.GetHashCode();

return nameHash ^ idHash ^ priceHash;
}
}

Now that we have our EqualityComparer set up for the Widget class, let’s look at how the Except method uses our implementation.

Test 1 – Two lists with same reference items.

[TestMethod]
public void Except_ListsWithSameReferenceItems_ShouldNotYieldDifferences()
{
var widget1 = new Widget { Id = 1, Name = “Widget1″, Price = 0.99 };
var widget2 = new Widget { Id = 2, Name = “Widget2″, Price = 0.99 };
var list1 = new List<Widget> { widget1, widget2 };
var list2 = new List<Widget> { widget1, widget2 };

var results = list1.Except(list2, new WidgetComparer());

Assert.IsTrue(results.Count() == 0);
}

Note in our implementation (which follows the example from MSDN) we check object.ReferenceEquals in the Equals method. But that check alone will not suffice, the result from GetHashCode is also validated so we must calculate the HashCode uniformly for the object as well.

Test 2 – Two lists with same items (but not same references)

[TestMethod]
public void Except_ListsWithSameItems_ShouldNotYieldDifferences()
{
var list1 = new List<Widget>
{
new Widget { Id = 1, Name = “Widget1″, Price = 0.99 },
new Widget { Id = 2, Name = “Widget2″, Price = 0.99 }
};
var list2 = new List<Widget>
{
new Widget { Id = 1, Name = “Widget1″, Price = 0.99 },
new Widget { Id = 2, Name = “Widget2″, Price = 0.99 }
};

var results = list1.Except(list2, new WidgetComparer());

Assert.IsTrue(results.Count() == 0);
}

In this test we use widgets set up with identical properties for each list. The test will still pass because our EqualityComparer evaluates the widgets using the formula we specified (Id, Name, & Price all match).

Test 3 – Two lists with different items. This one is tricky though, because it List 2 is the one that contains different items.

[TestMethod]
public void Except_List2WithDifferentItems_ShouldNotYieldDifferences()
{
var list1 = new List<Widget> {
new Widget { Id = 1, Name = “Widget1″, Price = 0.99 },
new Widget { Id = 2, Name = “Widget2″, Price = 0.99 }
};
var list2 = new List<Widget>{
new Widget {Id = 1, Name = “Widget1″, Price = 0.99},
new Widget {Id = 2, Name = “Widget2″, Price = 0.99},
new Widget { Id = 3, Name = “Widget3″, Price = 0.99 }
};

var results = list1.Except(list2, new WidgetComparer());

Assert.IsTrue(results.Count() == 0);
}

What gives? When you call Except you are saying “Give me everything that is in list 1, that is not in list 2.” In this case, list 2 has more items than list 1 and all the items from list 1 are in list 2.

Test 4 – Two lists with different items. This time list 1 has more items than list 2.

[TestMethod]
public void Except_List1WithDifferentItems_ShouldYieldDifferences()
{
var list1 = new List<Widget> {
new Widget { Id = 1, Name = “Widget1″, Price = 0.99 },
new Widget { Id = 2, Name = “Widget2″, Price = 0.99 },
new Widget { Id = 3, Name = “Widget3″, Price = 0.99 }
};
var list2 = new List<Widget>{
new Widget {Id = 1, Name = “Widget1″, Price = 0.99},
new Widget {Id = 2, Name = “Widget2″, Price = 0.99}
};

var results = list1.Except(list2, new WidgetComparer());

Assert.IsTrue(results.Count() == 1);
}

As expected, we find that Widget3 does not exist in list 2. Remember, when you call Except, say the mantra: “Give me everything that is in list 1, that is not in list 2.”

If you have any comments or improvements on this simple example of using Except, post away!

  • Share/Bookmark

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Tags: , , , , , ,

One Response to “How to Use .Except with EqualityComparer

  1. [...] This post was mentioned on Twitter by Philip. Philip said: LINQ: How to Use .Except with EqualityComparer http://bit.ly/cgZryC [...]

Leave a Reply