Last active
September 6, 2018 14:06
-
-
Save pmunin/b601a3e91f0ecf31bffc264e9e64326e to your computer and use it in GitHub Desktop.
C# Fluent Equality Comparing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
namespace Pmunin.Gists | |
{ | |
public static class EquatableExtensions | |
{ | |
/// <summary> | |
/// Generates equatable wrapper for the target and equality comparer | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="target"></param> | |
/// <param name="comparer"></param> | |
/// <returns></returns> | |
public static Equatable<T> ToEquatable<T>(this T target, IEqualityComparer<T> comparer) | |
{ | |
return new Equatable<T>(target, comparer); | |
} | |
public class Equatable<T> : IEquatable<Equatable<T>> | |
{ | |
public T Target { get; } | |
private IEqualityComparer<T> Comparer { get; } | |
public Equatable(T target, IEqualityComparer<T> comparer) | |
{ | |
this.Target = target; | |
this.Comparer = comparer; | |
} | |
public bool Equals(Equatable<T> equatable) | |
{ | |
var res = Comparer.Equals(this.Target, equatable.Target); | |
return res; | |
} | |
public override bool Equals(object obj) | |
{ | |
if (obj is Equatable<T> eq) | |
{ | |
return this.Equals(equatable: eq); | |
} | |
return this.Equals(obj); | |
} | |
public override int GetHashCode() | |
{ | |
var res = this.Comparer.GetHashCode(this.Target); | |
return res; | |
} | |
public static bool operator ==(Equatable<T> obj1, Equatable<T> obj2) | |
{ | |
return Object.Equals(obj1, obj2); | |
} | |
public static bool operator !=(Equatable<T> obj1, Equatable<T> obj2) | |
{ | |
return (obj1 != obj2); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
namespace Pmunin.Gists | |
{ | |
/// <summary> | |
/// Fluent utils for declaring EqualityComparer without defining new class | |
/// </summary> | |
public class FluentEqualityComparer { | |
private class TypeEqualityComparer<T> : EqualityComparer<T>, IEqualityComparer | |
{ | |
public TypeEqualityComparer(Func<T,T,bool> equals, Func<T,int> getHashCode, Func<T,T> preConvertValue=null) | |
{ | |
this.equals = equals; | |
this.getHashCode = getHashCode; | |
this.preConvertValue = preConvertValue; | |
} | |
Func<T, T, bool> equals; | |
private Func<T, int> getHashCode; | |
private Func<T, T> preConvertValue; | |
public override bool Equals(T x, T y) | |
{ | |
return this.equals(x, y); | |
} | |
bool IEqualityComparer.Equals(object x, object y) { | |
if (preConvertValue != null) | |
{ | |
x = preConvertValue((T)x); | |
y = preConvertValue((T)y); | |
} | |
if (x == y) | |
{ | |
return true; | |
} | |
if (x == null || y == null) | |
{ | |
return false; | |
} | |
if (x is T && y is T) | |
{ | |
return Equals((T)x, (T)y); | |
} | |
return false; | |
} | |
int IEqualityComparer.GetHashCode(object obj) | |
{ | |
if (preConvertValue != null) | |
{ | |
obj = preConvertValue((T)obj); | |
} | |
if (obj == null) | |
{ | |
return 0; | |
} | |
if (obj is T) | |
{ | |
return GetHashCode((T)obj); | |
} | |
//ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); | |
return 0; | |
} | |
public override int GetHashCode(T obj) | |
{ | |
return this.getHashCode(obj); | |
} | |
} | |
public static EqualityComparer<T> Of<T>(Func<T, T, bool> equals, Func<T, int> getHashCode, Func<T,T> preconvert=null) | |
{ | |
return new TypeEqualityComparer<T>(equals, getHashCode, preconvert); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
namespace Pmunin.Gists | |
{ | |
/// <summary> | |
/// equality comparer based on properties and values | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
public class PropertiesEqualityComparer<T> : EqualityComparer<T> | |
{ | |
/// <summary> | |
/// Instantiate equality comparer based on comparing properties and its values | |
/// </summary> | |
/// <param name="propFilter">(optional) filters the properties that should be involved in comparison. If no properties are involved, then comparator always return true</param> | |
/// <param name="getPropertyValueComparer">(optional) allow to provide equality comparer for each individual property, otherwise default value comparison will be used</param> | |
public PropertiesEqualityComparer(Func<PropertyInfo, bool> propFilter = null, Func<PropertyInfo, IEqualityComparer> getPropertyValueComparer=null) | |
{ | |
var props = typeof(T).GetProperties().AsEnumerable(); | |
if (propFilter != null) | |
{ | |
props = props.Where(propFilter); | |
} | |
this.propertiesToCompare = props.ToArray(); | |
this.getPropertyComparer = getPropertyValueComparer; | |
} | |
PropertyInfo[] propertiesToCompare; | |
private Func<PropertyInfo, IEqualityComparer> getPropertyComparer; | |
public override bool Equals(T x, T y) | |
{ | |
var diffs = GetDifferencies(x, y, (prop, xVal, yVal) => new { prop, xVal, yVal }); | |
//put a breakpoint here and watch diffs.ToArray() to see the properties with different values | |
return !diffs.Any(); | |
} | |
public IEnumerable<TDifference> GetDifferencies<TDifference>(T x, T y, Func<PropertyInfo, object, object, TDifference> onDifference) | |
{ | |
var propVals = this.propertiesToCompare.Select(p => new { | |
prop = p, | |
value1 = p.GetValue(x), | |
value2 = p.GetValue(y), | |
comparer = this.getPropertyComparer != null | |
? getPropertyComparer(p) | |
: null | |
}); | |
var diffs = propVals.Where(pv => !pv.comparer?.Equals(pv.value1, pv.value2)??!Object.Equals(pv.value1, pv.value2) | |
).Select(d=>onDifference(d.prop, d.value1, d.value2)); | |
return diffs; | |
} | |
public IEnumerable<TDifference> GetDifferencesMany<TDifference>(IEnumerable<T> xs, IEnumerable<T> ys, Func<T, T, PropertyInfo, object, object,TDifference> selectDifference) | |
{ | |
var xyDiffs = xs.Zip(ys, (x, y) => new { x, y }) | |
.SelectMany(xy=>this.GetDifferencies(xy.x, xy.y, (prop,xv, yv)=>selectDifference(xy.x,xy.y,prop,xv, yv))); | |
return xyDiffs; | |
} | |
public override int GetHashCode(T obj) | |
{ | |
var propVals = this.propertiesToCompare.Select(p => (value: p.GetValue(obj), comparer: getPropertyComparer?.Invoke(p))); | |
var res = 0; | |
unchecked | |
{ | |
foreach (var pv in propVals) | |
{ | |
var pvHashCode = pv.comparer?.GetHashCode(pv.value) ?? pv.value?.GetHashCode() ?? 0; | |
res = (res * 397) ^ pvHashCode; | |
} | |
} | |
return res; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment