Skip to content

Instantly share code, notes, and snippets.

@pmunin
Last active September 6, 2018 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmunin/b601a3e91f0ecf31bffc264e9e64326e to your computer and use it in GitHub Desktop.
Save pmunin/b601a3e91f0ecf31bffc264e9e64326e to your computer and use it in GitHub Desktop.
C# Fluent Equality Comparing
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);
}
}
}
}
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);
}
}
}
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