Skip to content

Instantly share code, notes, and snippets.

@Porges
Last active May 30, 2016 21:09
Show Gist options
  • Save Porges/8f52ac9b7f8984da1b241dc15647c9b6 to your computer and use it in GitHub Desktop.
Save Porges/8f52ac9b7f8984da1b241dc15647c9b6 to your computer and use it in GitHub Desktop.
Comparisons
using System;
using Comparisons;
namespace Types
{
class Person : Comparable<Person>
{
private static readonly Comparer<Person> _comparer =
Comparer<Person>
.On(_ => _.LastName, StringComparer.OrdinalIgnoreCase)
.ThenOn(_ => _.FirstName, StringComparer.OrdinalIgnoreCase);
protected override Comparer<Person> Comparer => _comparer;
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName)
{
if (firstName == null) throw new ArgumentNullException(nameof(firstName));
if (lastName == null) throw new ArgumentNullException(nameof(lastName));
FirstName = firstName;
LastName = lastName;
}
}
}
using System;
using System.Collections.Generic;
namespace Comparisons
{
// Equality and Comparer only really exist because IComparer<T> doesn't inherit from IEquatable<T>
public class Equality<T> : IEqualityComparer<T>
{
protected readonly IEqualityComparer<T> _equality;
internal Equality(IEqualityComparer<T> equality)
{
_equality = equality;
}
public bool Equals(T x, T y) => _equality.Equals(x, y);
public int GetHashCode(T obj) => _equality.GetHashCode(obj);
public static Equality<T> On<TKey>(Func<T, TKey> extractor)
=> On(extractor, EqualityComparer<TKey>.Default);
public static Equality<T> On<TKey>(Func<T, TKey> extractor, IEqualityComparer<TKey> comparer)
=> new Equality<T>(new LambdaEquality<T, TKey>(extractor, comparer));
public Equality<T> ThenOn<TKey>(Func<T, TKey> extractor)
=> ThenOn(extractor, EqualityComparer<TKey>.Default);
public Equality<T> ThenOn<TKey>(Func<T, TKey> extractor, IEqualityComparer<TKey> comparer)
=> new Equality<T>(new ChainedEquality<T>(_equality, new LambdaEquality<T, TKey>(extractor, comparer)));
}
// 'Comparison' is already stolen by the System namespace
public sealed class Comparer<T> : Equality<T>, IComparer<T>
{
private readonly IComparer<T> _comparison;
internal Comparer(IEqualityComparer<T> equality, IComparer<T> comparison)
: base(equality)
{
_comparison = comparison;
}
public int Compare(T x, T y) => _comparison.Compare(x, y);
public new static Comparer<T> On<TKey>(Func<T, TKey> extractor)
=> On(extractor, EqualityComparer<TKey>.Default, System.Collections.Generic.Comparer<TKey>.Default);
public static Comparer<T> On<TKey>(Func<T, TKey> extractor, IEqualityComparer<TKey> equality, IComparer<TKey> comparer)
=> new Comparer<T>(
new LambdaEquality<T, TKey>(extractor, equality),
new LambdaComparer<T, TKey>(extractor, comparer));
public new Comparer<T> ThenOn<TKey>(Func<T, TKey> extractor)
=> ThenOn(extractor, EqualityComparer<TKey>.Default, System.Collections.Generic.Comparer<TKey>.Default);
public Comparer<T> ThenOn<TKey>(Func<T, TKey> extractor, Comparer<TKey> comparer)
=> ThenOn(extractor, comparer._equality, comparer._comparison);
public Comparer<T> ThenOn<TKey>(Func<T, TKey> extractor, IEqualityComparer<TKey> equality, IComparer<TKey> comparer)
=> new Comparer<T>(
new ChainedEquality<T>(_equality, new LambdaEquality<T,TKey>(extractor, equality)),
new ChainedComparer<T>(_comparison, new LambdaComparer<T,TKey>(extractor, comparer)));
// StringComparer is both IEqualityComparer and IComparer, as desired:
public static Comparer<T> On(Func<T, string> extractor, StringComparer comparer)
=> On(extractor, comparer, comparer);
public Comparer<T> ThenOn(Func<T, string> extractor, StringComparer comparer)
=> ThenOn(extractor, comparer, comparer);
}
// Helper classes
internal sealed class LambdaComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> _extractor;
private readonly IComparer<TKey> _comparer;
public LambdaComparer(Func<T, TKey> extractor, IComparer<TKey> comparer)
{
_extractor = extractor;
_comparer = comparer;
}
public int Compare(T x, T y)
{
if (x == null)
{
if (y == null)
{
return 0;
}
return -1;
}
if (y == null)
{
return 1;
}
return _comparer.Compare(_extractor(x), _extractor(y));
}
}
internal sealed class LambdaEquality<T, TKey> : IEqualityComparer<T>
{
private readonly Func<T, TKey> _extractor;
private readonly IEqualityComparer<TKey> _comparer;
public LambdaEquality(Func<T, TKey> extractor, IEqualityComparer<TKey> comparer)
{
_extractor = extractor;
_comparer = comparer;
}
public bool Equals(T x, T y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
return _comparer.Equals(_extractor(x), _extractor(y));
}
public int GetHashCode(T obj) => obj == null ? 0 : _comparer.GetHashCode(_extractor(obj));
}
internal sealed class ChainedComparer<T> : IComparer<T>
{
private readonly IComparer<T> _previous;
private readonly IComparer<T> _next;
public ChainedComparer(IComparer<T> previous, IComparer<T> next)
{
_previous = previous;
_next = next;
}
public int Compare(T x, T y)
{
var first = _previous.Compare(x, y);
if (first != 0)
{
return first;
}
return _next.Compare(x, y);
}
}
internal sealed class ChainedEquality<T> : IEqualityComparer<T>
{
private readonly IEqualityComparer<T> _previous;
private readonly IEqualityComparer<T> _next;
public ChainedEquality(IEqualityComparer<T> previous, IEqualityComparer<T> next)
{
_previous = previous;
_next = next;
}
public bool Equals(T x, T y) => _previous.Equals(x, y) && _next.Equals(x, y);
public int GetHashCode(T obj) => _previous.GetHashCode(obj) * 13 + _next.GetHashCode(obj);
}
// Base class helpers
public abstract class Equatable<T> : IEquatable<T>
where T : Equatable<T>
{
public sealed override bool Equals(object obj) => Equals(obj as T);
public bool Equals(T other) => Equality.Equals((T)this, other);
public sealed override int GetHashCode() => Equality.GetHashCode((T)this);
protected abstract Equality<T> Equality { get; }
}
public abstract class Comparable<T> : Equatable<T>, IComparable<T>
where T : Comparable<T>
{
protected sealed override Equality<T> Equality => Comparer;
public int CompareTo(T other) => Comparer.Compare((T)this, other);
protected abstract Comparer<T> Comparer { get; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment