Enumerable.Except is not using my custom mapper
I'm trying to use the except method with a custom equality mapping, but it doesn't work.
My comparative equality coefficient:
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject
{
#region IEqualityComparer<T> Members
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <param name="x">The first object of type <paramref name="T"/> to compare.</param>
/// <param name="y">The second object of type <paramref name="T"/> to compare.</param>
/// <returns>
/// <see langword="true"/> If the specified objects are equal; otherwise, <see langword="false"/>.
/// </returns>
public bool Equals(T x, T y)
{
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <param name="obj">The object to get the hash code.</param>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
/// </exception>
public int GetHashCode(T obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
My exception:
BusinessObjectGuidEqualityComparer<Area> comparer = new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = this.Areas.Except(allocatedAreas, comparer);
IEnumerable<Area> toAdd = allocatedAreas.Except(this.Areas, comparer);
Strange thing: the event I provide to my custom equality mapper is the default, so what am I doing wrong?
Thanks for the help.
source to share
Like Marc, I just tested this, everything is called just fine, I assume you are caught in a lazy LINQ execution, notice the ToArray in my code.
Note that while tracking this down, I noticed that GetHashCode is never called for null objects in the comparator.
Keep in mind that MiscUtil has a wonderful way to do this, inline inline: Can I specify my explicit type for the inline comparator?
Or you can adapt this value to Except: Distinguishing List of Objects Based on Arbitrary Key in LINQ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
public class BusinessObject {
public Guid Guid { get; set; }
}
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject {
#region IEqualityComparer<T> Members
public bool Equals(T x, T y) {
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// </exception>
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
class Program {
static void Main(string[] args) {
var comparer = new BusinessObjectGuidEqualityComparer<BusinessObject>();
List<BusinessObject> list1 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()}
};
List<BusinessObject> list2 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()},
null,
null,
list1[0]
};
var toRemove = list1.Except(list2, comparer).ToArray();
var toAdd = list2.Except(list1, comparer).ToArray();
// toRemove.Length == 1
// toAdd.Length == 2
Console.ReadKey();
}
}
}
source to share
Try:
public int GetHashCode(T obj) {
return obj == null ? 0 : obj.Guid.GetHashCode();
}
Your hashcode must match (or at least not contradict) equality; and your equality says "zeros are equal, otherwise compare guid". Internally, I expect to Except
use HashSet<T>
, which explains why getting GetHashCode
right is so important .
Here's my test setup (using above GetHashCode
) that works great:
public abstract class BusinessObject {
public Guid Guid { get; set; }
}
class Area : BusinessObject {
public string Name { get; set; }
static void Main() {
Guid guid = Guid.NewGuid();
List<Area> areas = new List<Area> {
new Area { Name = "a", Guid = Guid.NewGuid() },
new Area { Name = "b", Guid = guid },
new Area { Name = "c", Guid = Guid.NewGuid() },
};
List<Area> allocatedAreas = new List<Area> {
new Area { Name = "b", Guid = guid},
new Area { Name = "d", Guid = Guid.NewGuid()},
};
BusinessObjectGuidEqualityComparer<Area> comparer =
new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = areas.Except(allocatedAreas, comparer);
foreach (var row in toRemove) {
Console.WriteLine(row.Name); // shows a & c, since b is allocated
}
}
}
If your version doesn't work you will have to post something about how you use it as it works great for me (see above).
source to share
The methods in your equality mapping are not the same. You are comparing the GUIDs of the objects, but the method GetHashCode
uses the default implementation based on the reference, not the GUID. Since different instances will receive different hash codes, even though they have the same GUID, the method Equals
will never be used.
Get a hashcode for a GUID in a method GetHashCode
as this is what you are comparing:
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.Guid.GetHashCode();
}
source to share