Common IEqualityComparer <T> and GetHashCode

Being a little lazy in implementing many IEqualityComparers, and given that I could not easily edit the class implementations of the object being compared, I went with the following, intended for use with the Distinct () and Except () extension methods.

public class GenericEqualityComparer<T> : IEqualityComparer<T> { Func<T, T, bool> compareFunction; Func<T, int> hashFunction; public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction) { this.compareFunction = compareFunction; this.hashFunction = hashFunction; } public bool Equals(T x, T y) { return compareFunction(x, y); } public int GetHashCode(T obj) { return hashFunction(obj); } } 

Seems pretty, but every time gives a hash function REALLY? I understand that the hash code is used to place objects in buckets. Different buckets, the object is not equal, nor is it called.

If GetHashCode returns the same value, equals is called. (from: Why is it important to override GetHashCode when the Equals method is overridden? )

So, what could go wrong if, for example, (and I hear many programmers scream in horror) GetHashCode returns a constant to force Equal to be called? A.

+9
c # gethashcode
May 6 '11 at 9:15
source share
5 answers

Nothing will go wrong, but in containers based on hash tables, you go from the approximate performance of O (1) to O (n) when doing a search. You would be better off just storing everything in a list and brute force, looking for it for elements that fulfill equality.

+12
May 6 '11 at 9:18
source share

If a common use case compares objects according to one of their properties, you can add an additional constructor and implement it and call it like this:

 public GenericEqualityComparer(Func<T, object> projection) { compareFunction = (t1, t2) => projection(t1).Equals(projection(t2)); hashFunction = t => projection(t).GetHashCode(); } var comaparer = new GenericEqualityComparer( o => o.PropertyToCompare); 

This will automatically use the hash implemented by this property.

EDIT: A more efficient and reliable implementation inspired my Marc comments:

 public static GenericEqualityComparer<T> Create<TValue>(Func<T, TValue> projection) { return new GenericEqualityComparer<T>( (t1, t2) => EqualityComparer<TValue>.Default.Equals( projection(t1), projection(t2)), t => EqualityComparer<TValue>.Default.GetHashCode(projection(t))); } var comparer = GenericEqualityComparer<YourObjectType>.Create( o => o.PropertyToCompare); 
+9
May 6 '11 at 9:27
source share

Your productivity will decrease. Distinct and Except are efficient operations when implemented in established data structures. By providing a constant hash value, you essentially destroy this characteristic and impose a naive algorithm using linear search.

You need to make sure that this is acceptable for your data volume. But for a few large datasets, the difference will be expressed. For example, Except will increase from the expected time O (n) to O (n²), which can be a big deal.

Instead of providing a constant, why not just call your own GetHashCode object method? This may not give much value, but it cannot be worse than using a constant, and the correctness will be preserved if the GetHashCode method of the object is not overridden to return incorrect values.

+1
May 6 '11 at 9:22 a.m.
source share

Found this code in CodeProject - General IEqualityComparer template for Linq Distinct () , executed beautifully.

Use Case:

 IEqualityComparer<Contact> c = new PropertyComparer<Contact>("Name"); IEnumerable<Contact> distinctEmails = collection.Distinct(c); 

Common IEqualityComparer

 public class PropertyComparer<T> : IEqualityComparer<T> { private PropertyInfo _PropertyInfo; /// <summary> /// Creates a new instance of PropertyComparer. /// </summary> /// <param name="propertyName">The name of the property on type T /// to perform the comparison on.</param> public PropertyComparer(string propertyName) { //store a reference to the property info object for use during the comparison _PropertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public); if (_PropertyInfo == null) { throw new ArgumentException(string.Format("{0} is not a property of type {1}.", propertyName, typeof(T))); } } #region IEqualityComparer<T> Members public bool Equals(T x, T y) { //get the current value of the comparison property of x and of y object xValue = _PropertyInfo.GetValue(x, null); object yValue = _PropertyInfo.GetValue(y, null); //if the xValue is null then we consider them equal if and only if yValue is null if (xValue == null) return yValue == null; //use the default comparer for whatever type the comparison property is. return xValue.Equals(yValue); } public int GetHashCode(T obj) { //get the value of the comparison property out of obj object propertyValue = _PropertyInfo.GetValue(obj, null); if (propertyValue == null) return 0; else return propertyValue.GetHashCode(); } #endregion } 
+1
Mar 05 '14 at 13:12
source share

Try this code:

 public class GenericCompare<T> : IEqualityComparer<T> where T : class { private Func<T, object> _expr { get; set; } public GenericCompare(Func<T, object> expr) { this._expr = expr; } public bool Equals(T x, T y) { var first = _expr.Invoke(x); var sec = _expr.Invoke(y); if (first != null && first.Equals(sec)) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } } 

Example: collection = collection.Except (ExistingDataEles, new GenericCompare (x => x.Id)). ToList ();

-one
May 13 '14 at 6:12
source share



All Articles