I try to use the except method with a custom equality comparer, but it is not work.
My equality comparer:
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 except usage:
BusinessObjectGuidEqualityComparer<Area> comparer = new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = this.Areas.Except(allocatedAreas, comparer);
IEnumerable<Area> toAdd = allocatedAreas.Except(this.Areas, comparer);
The strange thing is, event I provide my custom equality comparer the default one is used, so what do I make wrong?
Thanks for help.
Similar to Marc I just tested this, everything is being called just fine, my guess is that you are caught by the LINQ deferred execution, notice the ToArray in my code.
Note, when tracing through this I noticed GetHashCode is never called on null objects in the comparer.
Keep in mind, MiscUtil has an awesome way for you to do this stuff inline, see: Can I specify my explicit type comparator inline?
Or you could adapt this to Except: Distinct list of objects based on an 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();
}
}
}
Try:
public int GetHashCode(T obj) {
return obj == null ? 0 : obj.Guid.GetHashCode();
}
Your hash-code must match equality (or at least, not contradict it); and your equality says "nulls are equal, otherwise compare the guid". Internally, I expect Except
uses a HashSet<T>
, which explains why getting GetHashCode
right is so important.
Here's my test rig (using the above GetHashCode
) which works fine:
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're going to have to post something about how you're using it, as it works fine for me (above).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With