patterncsharpMinor
Automatic IEqualityComparer<T>
Viewed 0 times
automaticiequalitycomparerstackoverflow
Problem
There are APIs like the
I implemented this interface in a reusable fashion so that I can use it with any value and any number of properties.
I then build a new extension which also can accept any number of selectors to compare:
Example:
Select all properties of an exception that are not in the base exception:
Except extension that require the IEqualityComparer to work. I find it's too much work for such a simple task to implement an interface so I thought why not automate it.I implemented this interface in a reusable fashion so that I can use it with any value and any number of properties.
internal class AutoEqualityComparer : IEqualityComparer
{
public AutoEqualityComparer(IEnumerable> selectors)
{
Selectors = selectors;
}
private IEnumerable> Selectors { get; }
public bool Equals(T left, T right)
{
return
!ReferenceEquals(left, null) &&
!ReferenceEquals(right, null) &&
Selectors.All(selector => selector(left).Equals(selector(right)));
}
public int GetHashCode(T obj)
{
unchecked
{
return Selectors
.Select(selector => selector(obj).GetHashCode())
.Aggregate(17, (hashCode, subHashCode) => hashCode * 31 + subHashCode);
}
}
}I then build a new extension which also can accept any number of selectors to compare:
internal static class Enumerable
{
public static IEnumerable Except(
this IEnumerable first,
IEnumerable second,
params Func[] compare)
{
var mec = new AutoEqualityComparer(compare);
return first.Except(second, mec);
}
}Example:
Select all properties of an exception that are not in the base exception:
var exceptionProperties =
typeof(ArgumentException)
.GetProperties()
.Except(typeof(Exception).GetProperties(), x => x.Name);
// result: ParamName is the only propertySolution
I think if you created a projection and pass that in the class would be easier to use and more readable for anyone coming after you.
Instead of the
Since we now have the strong type of class in equals we can use the EqualityComparer class for both the Equals and
I would create a class to create the
Now with the extension method you can create Tuples or anonymous classes for the
Example could be
Now to me this is more clear on what we are comparing and anonymous type compare the properties to see if they are equal and not reference.
Instead of the
IEnumerable> I would change it to be Func projection and lose the IEnumerable. Which would need to make the class now take two generics public class AutoEqualityComparer : IEqualityComparer
{
private readonly Func _projection;
public AutoEqualityComparer(Func projection)
{
_projection = projection;
}Since we now have the strong type of class in equals we can use the EqualityComparer class for both the Equals and
GetHashCode methods. public virtual bool Equals(T x, T y)
{
if (x == null && y == null)
{
return true;
}
if (x == null)
{
return false;
}
if (y == null)
{
return false;
}
var xData = _projection(x);
var yData = _projection(y);
return EqualityComparer.Default.Equals(xData, yData);
}
public virtual int GetHashCode(T obj)
{
if (obj == null)
{
return 0;
}
var objData = _projection(obj);
return EqualityComparer.Default.GetHashCode(objData);
}I would create a class to create the
AutoEqualityComparer to make it easier to usepublic class EqualityProjectionComparer
{
public static AutoEqualityComparer Create(Func projection)
{
return new AutoEqualityComparer(projection);
}
}Now with the extension method you can create Tuples or anonymous classes for the
IEqualityComparerExample could be
var comparer = EqualityProjectionComparer.Create(arg => new
{
arg.ParamName,
arg.Message
});Now to me this is more clear on what we are comparing and anonymous type compare the properties to see if they are equal and not reference.
Code Snippets
public class AutoEqualityComparer<T, K> : IEqualityComparer<T>
{
private readonly Func<T, K> _projection;
public AutoEqualityComparer(Func<T, K> projection)
{
_projection = projection;
}public virtual bool Equals(T x, T y)
{
if (x == null && y == null)
{
return true;
}
if (x == null)
{
return false;
}
if (y == null)
{
return false;
}
var xData = _projection(x);
var yData = _projection(y);
return EqualityComparer<K>.Default.Equals(xData, yData);
}
public virtual int GetHashCode(T obj)
{
if (obj == null)
{
return 0;
}
var objData = _projection(obj);
return EqualityComparer<K>.Default.GetHashCode(objData);
}public class EqualityProjectionComparer<T>
{
public static AutoEqualityComparer<T, K> Create<K>(Func<T, K> projection)
{
return new AutoEqualityComparer<T, K>(projection);
}
}var comparer = EqualityProjectionComparer<ArgumentException>.Create(arg => new
{
arg.ParamName,
arg.Message
});Context
StackExchange Code Review Q#147697, answer score: 4
Revisions (0)
No revisions yet.