As part of the Open Platform client library I have created a type for the tags that are returned by the api:
//Attributes omitted for brevity public class Tag { public string Name { get; set; } public string Type { get; set; } public string Filter { get; set; } public string ApiUrl { get; set; } public string WebUrl { get; set; } }
The Filter property acts like a primary key so if two Tag objects have the same Filter then they should be considered equal.
This test describes the behaviour I want:
[Test] public void Test_tag_equality() { Tag t1 = new Tag { Filter = "/Rupert" }; Tag t2 = new Tag { Filter = "/Rupert" }; Tag t3 = new Tag { Filter = "/Harry" }; Tag t4 = t3; Assert.AreEqual(t1, t2); Assert.AreNotEqual(t1, t3); Assert.AreNotSame(t1, t2); Assert.AreSame(t3, t4); }
So I overrode Equals
public override bool Equals(object obj) { return Filter == ((Tag) obj).Filter; }
and my test passed.
However later on I wanted to use IEnumerable.Intersection() to find all the elements from one list that were also in another list as follows:
//Get the tags which match with music types var musicTypes = new[] { new Tag{Filter="/music/popandrock"}, new Tag{Filter="/music/classicalmusicandopera"}, new Tag{Filter="/music/electronicmusic"}, new Tag{Filter="/music/urban"}, new Tag{Filter="/music/folk"}, new Tag{Filter="/music/worldmusic"} }; //filter the results to get the ones which are tagged with the musicType tags var filtered = results.Results.Where(c => c.TaggedWith.Intersect(musicTypes).Count() > 0);
but my intersection always returned 0 matches, in other words the way that Intersect() was comparing the equality of the elements of my IEnumerable was not using my Equals() override (or not exclusively anyway). There is an overload for this function that takes a custom EqualityComparer, but I didn’t want to use that.
I investigated a bit more and found that IEnumerable uses the IEquatable interface to determine whether elements are equal so I implemented this and thought that would be that:
public class Tag : IEquatable<Tag> { ... #region IEquatable<Tag> Members public bool Equals(Tag other) { return Filter == other.Filter; } #endregion }
but my intersection still failed to return any results.
After quite a bit of head scratching I tried implementing GetHashCode() as well and finally it worked
//The final implementation public class Tag : IEquatable<Tag> { ... public override bool Equals(object obj) { return Filter == ((Tag)obj).Filter; } public override int GetHashCode() { return Filter.GetHashCode(); } #region IEquatable<Tag> Members public bool Equals(Tag other) { return Filter == other.Filter; } #endregion }
Stepping through the code with the debugger it seems that all of these methods are called. It actually works correctly without the IEquatable implementation, but I have left it in for completeness.