[C# 4 и 3] Построитель компараторов
От: _FRED_ Черногория
Дата: 11.08.10 08:40
Оценка: 73 (7) +1
Заранее извиняюсь за большое количество кода в сообщении, но ведь и в форум и пишу специфический

Зачем это нужно и что это такое


Реализуя давеча в очередном объекте с несколькими (> 3) полями операции сравнения заметил, что происходит довольно много дублирования кода: при реализации Equals, GetHashCode и CompareTo в некотором разрезе делается одно и тоже — над всеми полями проводятся одни и те же монотонные действия.

Допустим, например, есть у нас такие вот объекты, с реализованными в них операциями сравнения:
  Структура
  struct V : IEquatable<V>, IComparable, IComparable<V>
  {
    public V(int a, string b, object c)
      : this() {
      A = a;
      B = b;
      C = c;
    }

    public int A { get; private set; }
    public string B { get; private set; }
    public object C { get; private set; }

    public override bool Equals(object obj) {
      return obj is V && Equals((V)obj);
    }

    public override int GetHashCode() {
      return A.GetHashCode()
        ^ StringComparer.Ordinal.GetHashCode(B)
        ^ (C == null ? 0 : C.GetHashCode());
    }

    public bool Equals(V other) {
      return A == other.A
        && StringComparer.Ordinal.Equals(B, other.B)
        && Equals(C, C);
    }

    int IComparable.CompareTo(object obj) {
      if(!(obj is V)) {
        throw new ArgumentException("!(obj is V)", "obj");
      }//if

      return CompareTo((V)obj);
    }

    public int CompareTo(V other) {
      var compare = A.CompareTo(other.A);
      if(compare != 0) {
        return compare;
      } else if((compare = StringComparer.Ordinal.Compare(B, other.B)) != 0) {
        return compare;
      } else {
        return Comparer<object>.Default.Compare(C, other.C);
      }//if
    }

    public static bool operator ==(V left, V right) {
      return left.Equals(right);
    }

    public static bool operator !=(V left, V right) {
      return !(left == right);
    }

    public static bool operator <(V left, V right) {
      return left.CompareTo(right) < 0;
    }

    public static bool operator <=(V left, V right) {
      return left.CompareTo(right) <= 0;
    }

    public static bool operator >(V left, V right) {
      return !(left <= right);
    }

    public static bool operator >=(V left, V right) {
      return !(left < right);
    }
  }
  Класс
  class R : IEquatable<R>, IComparable, IComparable<R>
  {
    public R(int a, string b, object c) {
      A = a;
      B = b;
      C = c;
    }

    public int A { get; private set; }
    public string B { get; private set; }
    public object C { get; private set; }

    public override bool Equals(object obj) {
      return Equals(obj as R);
    }

    public override int GetHashCode() {
      return A.GetHashCode()
        ^ StringComparer.Ordinal.GetHashCode(B)
        ^ (C == null ? 0 : C.GetHashCode());
    }

    public bool Equals(R other) {
      return other != null
        && A == other.A
        && StringComparer.Ordinal.Equals(B, other.B)
        && Equals(C, C);
    }

    int IComparable.CompareTo(object obj) {
      var other = obj as R;
      if(obj != null && (object)other == null) {
        throw new ArgumentException("obj != null && !(obj is R)", "obj");
      }//if
      return CompareTo((R)obj);
    }

    public int CompareTo(R other) {
      if(other == null) {
        return 1;
      } else {
        var compare = A.CompareTo(other.A);
        if(compare != 0) {
          return compare;
        } else if((compare = StringComparer.Ordinal.Compare(B, other.B)) != 0) {
          return compare;
        } else {
          return Comparer<object>.Default.Compare(C, other.C);
        }//if
      }//if
    }

    public static bool operator ==(R left, R right) {
      return Equals(left, right);
    }

    public static bool operator !=(R left, R right) {
      return !(left == right);
    }

    public static bool operator <(R left, R right) {
      if(ReferenceEquals(left, null)) {
        return !ReferenceEquals(right, null) && right.CompareTo(left) > 0;
      } else {
        return left.CompareTo(right) < 0;
      }//if
    }

    public static bool operator <=(R left, R right) {
      if(ReferenceEquals(left, null)) {
        return !ReferenceEquals(right, null) && right.CompareTo(left) >= 0;
      } else {
        return left.CompareTo(right) <= 0;
      }//if
    }

    public static bool operator >(R left, R right) {
      return !(left <= right);
    }

    public static bool operator >=(R left, R right) {
      return !(left < right);
    }
  }

В методах сравнения идёт обращение к данным одного объекта, другого объекта и с помощью некоего механизма (компаратора, оператора == и т.п.) вычисляется значение, которое неким образом "суммируется" (ксорится, логически умножается) с подобными значениями от других данных объектов. Причём, самые разные объекты могут отличаться своими данными и способами их сравнения (например, с учётом регистра или нет) а операции в подавляющем большинстве случаев совершенно одинаковы.

Поэтому напрашивается какое-то такое колдовство:
    var builder = new ComparerBuilder<X>()
      .Add(x => x.A)
      .Add(x => x.B, StringComparer.Ordinal)
      .Add(x => x.C);
    var equality = builder.ToEqualityComparer();
    var comparison = builder.ToComparer();

то есть некий объект, которому сообщается о том, какие данные нужно сравнивать и каким механизмом это делать, а всю обвязку с проверками, поочерёдным обходом данных и агрегирования результата он возьмёт на себя. Тогда реализация сравнения в объектах станет менее умной и поддающейся простейшей генерации или "сниппетизации":
  Структура
  struct V : IEquatable<V>, IComparable, IComparable<V>
  {
    private static readonly ComparerBuilder<V> Builder = new ComparerBuilder<V>()
      .Add(x => x.A)
      .Add(x => x.B, StringComparer.Ordinal)
      .AddDefault(x => x.C);

    private static readonly EqualityComparer<V> Equality = Builder.ToEqualityComparer();
    private static readonly Comparer<V> Comparison = Builder.ToComparer();

    public V(int a, string b, object c)
      : this() {
      A = a;
      B = b;
      C = c;
    }

    public int A { get; private set; }
    public string B { get; private set; }
    public object C { get; private set; }

    public override bool Equals(object obj) {
      return obj is V && Equals((V)obj);
    }

    public override int GetHashCode() {
      return Equality.GetHashCode(this);
    }

    public bool Equals(V other) {
      return Equality.Equals(this, other);
    }

    int IComparable.CompareTo(object obj) {
      if(!(obj is V)) {
        throw new ArgumentException("!(obj is V)", "obj");
      }//if

      return CompareTo((V)obj);
    }

    public int CompareTo(V other) {
      return Comparison.Compare(this, other);
    }

    public static bool operator ==(V left, V right) {
      return left.Equals(right);
    }

    public static bool operator !=(V left, V right) {
      return !(left == right);
    }

    public static bool operator <(V left, V right) {
      return left.CompareTo(right) < 0;
    }

    public static bool operator <=(V left, V right) {
      return left.CompareTo(right) <= 0;
    }

    public static bool operator >(V left, V right) {
      return !(left <= right);
    }

    public static bool operator >=(V left, V right) {
      return !(left < right);
    }
  }

  Класс
class R : IEquatable<R>, IComparable, IComparable<R>
  {
    private static readonly ComparerBuilder<R> Builder = new ComparerBuilder<R>()
      .Add(x => x.A)
      .Add(x => x.B, StringComparer.Ordinal)
      .AddDefault(x => x.C);

    private static readonly EqualityComparer<R> Equality = Builder.ToEqualityComparer();
    private static readonly Comparer<R> Comparison = Builder.ToComparer();

    public R(int a, string b, object c) {
      A = a;
      B = b;
      C = c;
    }

    public int A { get; private set; }
    public string B { get; private set; }
    public object C { get; private set; }

    public override bool Equals(object obj) {
      return Equals(obj as R);
    }

    public override int GetHashCode() {
      return Equality.GetHashCode(this);
    }

    public bool Equals(R other) {
      return Equality.Equals(this, other);
    }

    int IComparable.CompareTo(object obj) {
      var other = obj as R;
      if(obj != null && (object)other == null) {
        throw new ArgumentException("obj != null && !(obj is R)", "obj");
      }//if
      return CompareTo((R)obj);
    }

    public int CompareTo(R other) {
      return Comparison.Compare(this, other);
    }

    public static bool operator ==(R left, R right) {
      return Equals(left, right);
    }

    public static bool operator !=(R left, R right) {
      return !(left == right);
    }

    public static bool operator <(R left, R right) {
      return Comparison.Compare(left, right) < 0;
    }

    public static bool operator <=(R left, R right) {
      return Comparison.Compare(left, right) <= 0;
    }

    public static bool operator >(R left, R right) {
      return !(left <= right);
    }

    public static bool operator >=(R left, R right) {
      return !(left < right);
    }
  }


Как видно, при изменении набора данных (полей класса) и способов сравнения различных полей, можифицировать теперь нужно лишь одно место: создание билдера, реализации же сравнения могут оставаться неизменными.

Реализация на четвёртом фреймворке

  Сам класс билдера
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace GBricks.Collections
{
  public class ComparerBuilder<T>
  {
    private static readonly ConstantExpression Null = Expression.Constant(null);
    private static readonly ConstantExpression Zero = Expression.Constant(0);

    private static readonly ConstantExpression One = Expression.Constant(1);
    private static readonly ConstantExpression MinusOne = Expression.Constant(-1);

    private static readonly ParameterExpression Left = Expression.Parameter(typeof(T), null);
    private static readonly ParameterExpression Right = Expression.Parameter(typeof(T), null);

    private static readonly ParameterExpression Compare = Expression.Parameter(typeof(int));
    private static readonly IEnumerable<ParameterExpression> CompareValiables = Enumerable.Repeat(Compare, 1);
    private static readonly LabelTarget Return = Expression.Label(typeof(int));
    private static readonly LabelExpression LabelZero = Expression.Label(Return, Zero);
    private static readonly GotoExpression ReturnZero = Expression.Return(Return, Zero);
    private static readonly GotoExpression ReturnOne = Expression.Return(Return, One);
    private static readonly GotoExpression ReturnMinusOne = Expression.Return(Return, MinusOne);
    private static readonly GotoExpression ReturnCompare = Expression.Return(Return, Compare);

    private static readonly Func<object, object, bool> EqualsDelegate = Object.Equals;
    private static readonly Func<int> GetHashCodeDelegate = new object().GetHashCode;
    private static readonly Func<int, int, int> RotateRightDelegate = Comparers.RotateRight;

    private static readonly bool IsValueType = typeof(T).IsValueType;

    public ComparerBuilder() {
      EqualsExpressions = new List<Expression>();
      GetHashCodeExpressions = new List<Expression>();
      CompareExpressions = new List<Expression>();
    }

    private IList<Expression> EqualsExpressions { get; set; }
    private IList<Expression> GetHashCodeExpressions { get; set; }
    private IList<Expression> CompareExpressions { get; set; }

    #region Expression Helpers

    private static BinaryExpression ReferenceEqual(Expression left, Expression right) {
      return Expression.ReferenceEqual(left, right);
    }

    private static BinaryExpression IsNull(Expression value) {
      return Expression.ReferenceEqual(value, Null);
    }

    private static BinaryExpression IsNotNull(Expression value) {
      return Expression.ReferenceNotEqual(value, Null);
    }

    #endregion Expression Helpers

    protected virtual Expression MakeEquals<P>(Expression left, Expression right, IEqualityComparer<P> comparer) {
      if(left == null) {
        throw new ArgumentNullException("left");
      } else if(right == null) {
        throw new ArgumentNullException("right");
      }//if

      if(comparer != null) {
        // return comparer.Equals(left, right);
        Func<P, P, bool> @delegate = comparer.Equals;
        var instance = Expression.Constant(comparer);
        return Expression.Call(instance, @delegate.Method, left, right);
      } else {
        if(typeof(P).IsValueType) {
          // return left == right;
          return Expression.Equal(left, right);
        } else {
          // return Object.Equals(left, right);
          return Expression.Call(EqualsDelegate.Method, left, right);
        }//if
      }//if
    }

    protected virtual Expression MakeGetHashCode<P>(Expression value, IEqualityComparer<P> comparer) {
      if(value == null) {
        throw new ArgumentNullException("value");
      }//if

      if(comparer != null) {
        // return comparer.GetHashCode(left);
        Func<P, int> @delegate = comparer.GetHashCode;
        var instance = Expression.Constant(comparer);
        return Expression.Call(instance, @delegate.Method, value);
      } else {
        if(typeof(P).IsValueType) {
          // return left.GetHashCode();
          return Expression.Call(value, GetHashCodeDelegate.Method);
        } else {
          // return Comparers.GetHashCode(left); [(left == null) ? 0 : left.GetHashCode();]
          Func<P, int> @delegate = Comparers.GetHashCode;
          return Expression.Call(@delegate.Method, value);
        }//if
      }//if
    }

    protected virtual Expression MakeCompare<P>(Expression left, Expression right, IComparer<P> comparer) {
      if(left == null) {
        throw new ArgumentNullException("left");
      } else if(right == null) {
        throw new ArgumentNullException("right");
      }//if

      if(comparer != null) {
        // return comparer.Compare(left, right);
        Func<P, P, int> @delegate = comparer.Compare;
        var instance = Expression.Constant(comparer);
        return Expression.Call(instance, @delegate.Method, left, right);
      } else {
        // return (left < right) ? -1 : (left > right ? 1 : 0);
        return Expression.Condition(Expression.LessThan(left, right), MinusOne,
          Expression.Condition(Expression.GreaterThan(left, right), One, Zero));
      }//if
    }

    private void AddCore<P>(Tuple<Expression, Expression> args, IEqualityComparer<P> comparer) {
      if(args == null) {
        throw new ArgumentNullException("args");
      }//if

      var equals = MakeEquals(args.Item1, args.Item2, comparer);
      EqualsExpressions.Add(equals);

      var hash = MakeGetHashCode(args.Item1, comparer);
      GetHashCodeExpressions.Add(hash);
    }

    private void AddCore<P>(Tuple<Expression, Expression> args, IComparer<P> comparer) {
      if(args == null) {
        throw new ArgumentNullException("args");
      }//if

      var compare = MakeCompare(args.Item1, args.Item2, comparer);
      CompareExpressions.Add(compare);
    }

    private sealed class ReplaceVisitor : ExpressionVisitor
    {
      public ReplaceVisitor(Expression what, Expression to) {
        if(what == null) {
          throw new ArgumentNullException("what");
        } else if(to == null) {
          throw new ArgumentNullException("to");
        }//if


        What = what;
        To = to;
      }

      public Expression What { get; private set; }
      public Expression To { get; private set; }

      public override Expression Visit(Expression node) {
        if(node == What) {
          return To;
        }//if

        return base.Visit(node);
      }
    }

    private static Tuple<Expression, Expression> Parameters<P>(Expression<Func<T, P>> expression) {
      if(expression == null) {
        throw new ArgumentNullException("expression");
      } else if(expression.Parameters.Count != 1) {
        throw new ArgumentException("expression.Parameters.Count != 1", "expression");
      }//if

      var left = new ReplaceVisitor(expression.Parameters[0], Left);
      var right = new ReplaceVisitor(expression.Parameters[0], Right);
      return Tuple.Create(left.Visit(expression.Body), right.Visit(expression.Body));
    }

    public ComparerBuilder<T> Add<P>(Expression<Func<T, P>> expression, IEqualityComparer<P> equality, IComparer<P> comparison) {
      var args = Parameters(expression);
      AddCore(args, equality);
      AddCore(args, comparison);
      return this;
    }

    public ComparerBuilder<T> Add<P, C>(Expression<Func<T, P>> expression, C comparer) where C : IEqualityComparer<P>, IComparer<P> {
      return Add(expression, comparer, comparer);
    }

    public ComparerBuilder<T> AddEquality<P>(Expression<Func<T, P>> expression, IEqualityComparer<P> equality = null) {
      var args = Parameters(expression);
      AddCore(args, equality);
      return this;
    }

    public ComparerBuilder<T> AddComparison<P>(Expression<Func<T, P>> expression, IComparer<P> comparison = null) {
      var args = Parameters(expression);
      AddCore(args, comparison);
      return this;
    }

    public ComparerBuilder<T> AddDefault<P>(Expression<Func<T, P>> expression) {
      return Add(expression, EqualityComparer<P>.Default, Comparer<P>.Default);
    }

    public ComparerBuilder<T> Add<P>(Expression<Func<T, P>> expression) {
      return Add(expression, null, null);
    }

    private static Expression<Func<T, T, bool>> BuildEquals(IEnumerable<Expression> items) {
      if(items == null) {
        throw new ArgumentNullException("items");
      }//if

      var expression = items.Aggregate(Expression.AndAlso);
      var body = IsValueType
        ? expression
        // return (object)x == (object)y || ((object)x != null && (object)y != null && expression);
        : Expression.OrElse(
            ReferenceEqual(Left, Right),
            Expression.AndAlso(
              Expression.AndAlso(IsNotNull(Left), IsNotNull(Right)),
              expression));
      return Expression.Lambda<Func<T, T, bool>>(body, Left, Right);
    }

    private static Expression<Func<T, int>> BuildGetHashCode(IEnumerable<Expression> items) {
      if(items == null) {
        throw new ArgumentNullException("items");
      }//if

      var expression = items.Skip(1).Select((item, index) => Tuple.Create(item, index + 1))
        .Aggregate(items.First(), (acc, item) =>
          Expression.ExclusiveOr(acc,
            Expression.Call(RotateRightDelegate.Method, item.Item1, Expression.Constant(item.Item2))));
      var body = IsValueType
        ? expression
        // return ((object)x == null) ? 0 : expression;
        : Expression.Condition(IsNull(Left), Zero, expression);
      return Expression.Lambda<Func<T, int>>(body, Left);
    }

    private static Expression<Func<T, T, int>> BuildCompare(IEnumerable<Expression> items) {
      if(items == null) {
        throw new ArgumentNullException("items");
      }//if

      var reverse = items.Reverse();
      Expression seed = Expression.Return(Return, reverse.First());
      var expression = reverse.Skip(1).Aggregate(seed,
        (acc, value) => Expression.IfThenElse(
          Expression.NotEqual(Expression.Assign(Compare, value), Zero), ReturnCompare, acc));
      var body = IsValueType
        ? expression
        //if((object)x == (object)y) {
        //  return 0;
        //} else if((object)x == null) {
        //  return -1;
        //} else if((object)y == null) {
        //  return 1;
        //} else {
        //  return expression;
        //}//if
        : Expression.IfThenElse(ReferenceEqual(Left, Right), ReturnZero,
            Expression.IfThenElse(IsNull(Left), ReturnMinusOne,
              Expression.IfThenElse(IsNull(Right), ReturnOne, expression)));
      var block = Expression.Block(CompareValiables, body, LabelZero);
      return Expression.Lambda<Func<T, T, int>>(block, Left, Right);
    }

    public EqualityComparer<T> ToEqualityComparer() {
      if(EqualsExpressions.Count == 0 || GetHashCodeExpressions.Count == 0) {
        return Comparers.EmptyEqualityComparer<T>();
      }//if
      Debug.Assert(EqualsExpressions.Count == GetHashCodeExpressions.Count);

      var equals = BuildEquals(EqualsExpressions);
      var hash = BuildGetHashCode(GetHashCodeExpressions);
      return Comparers.Create(equals.Compile(), hash.Compile());
    }

    public Comparer<T> ToComparer() {
      if(CompareExpressions.Count == 0) {
        return Comparers.EmptyComparer<T>();
      }//if

      var compare = BuildCompare(CompareExpressions);
      return Comparers.Create(compare.Compile());
    }
  }
}


Используется класс c методами-помощниками Comparers:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace GBricks.Collections
{
  public static class Comparers
  {
    public static EqualityComparer<T> EmptyEqualityComparer<T>() {
      return ConstEqualityComparer<T>.Default;
    }

    public static Comparer<T> EmptyComparer<T>() {
      return ConstComparer<T>.Default;
    }

    public static EqualityComparer<T> Const<T>(bool equals, int hash) {
      return new ConstEqualityComparer<T>(equals, hash);
    }

    public static Comparer<T> Const<T>(int compare) {
      return new ConstComparer<T>(compare);
    }

    public static EqualityComparer<T> Create<T>(Func<T, T, bool> equals, Func<T, int> hash) {
      if(equals == null) {
        throw new ArgumentNullException("equals");
      }//if

      return new MethodEqualityComparer<T>(equals, hash);
    }

    public static EqualityComparer<T> Create<T>(Func<T, T, bool> equals) {
      return Create(equals, item => (item == null) ? 0 : item.GetHashCode());
    }

    public static Comparer<T> Create<T>(Func<T, T, int> compare) {
      if(compare == null) {
        throw new ArgumentNullException("compare");
      }//if

      return new MethodComparer<T>(compare);
    }

    public static int RotateRight(int value, int places) {
      if((places &= 0x1F) == 0) {
        return value;
      }//if

      var mask = ~0x7FFFFFFF >> (places - 1);
      return ((value >> places) & ~mask) | ((value << (32 - places)) & mask);
    }

    public static int GetHashCode<X>(X value) {
      if(value == null) {
        return 0;
      }//if

      return value.GetHashCode();
    }

    [Serializable]
    private sealed class MethodEqualityComparer<T> : EqualityComparer<T>
    {
      public MethodEqualityComparer(Func<T, T, bool> equals, Func<T, int> hash) {
        if(equals == null) {
          throw new ArgumentNullException("equals");
        } else if(hash == null) {
          throw new ArgumentNullException("hash");
        }//if

        EqualsMethod = equals;
        GetHashCodeMethod = hash;
      }

      private Func<T, T, bool> EqualsMethod { get; set; }

      private Func<T, int> GetHashCodeMethod { get; set; }

      public override bool Equals(T x, T y) {
        return EqualsMethod(x, y);
      }

      public override int GetHashCode(T obj) {
        return GetHashCodeMethod(obj);
      }
    }

    [Serializable]
    private sealed class MethodComparer<T> : Comparer<T>
    {
      public MethodComparer(Func<T, T, int> compare) {
        if(compare == null) {
          throw new ArgumentNullException("compare");
        }//if

        CompareMethod = compare;
      }

      private Func<T, T, int> CompareMethod { get; set; }

      public override int Compare(T x, T y) {
        return CompareMethod(x, y);
      }
    }

    [Serializable]
    private sealed class ConstEqualityComparer<T> : EqualityComparer<T>
    {
      private static readonly EqualityComparer<T> @default = new ConstEqualityComparer<T>(true, 0);

      public ConstEqualityComparer(bool equals, int hash) {
        EqualsValue = equals;
        GetHashCodeValue = hash;
      }

      public static new EqualityComparer<T> Default {
        [DebuggerStepThrough]
        get { return @default; }
      }

      private bool EqualsValue { get; set; }

      private int GetHashCodeValue { get; set; }

      public override bool Equals(T x, T y) {
        return EqualsValue;
      }

      public override int GetHashCode(T obj) {
        return GetHashCodeValue;
      }
    }

    [Serializable]
    private sealed class ConstComparer<T> : Comparer<T>
    {
      private static readonly Comparer<T> @default = new ConstComparer<T>(0);

      public ConstComparer(int compare) {
        CompareValue = compare;
      }

      public static new Comparer<T> Default {
        [DebuggerStepThrough]
        get { return @default; }
      }

      private int CompareValue { get; set; }

      public override int Compare(T x, T y) {
        return CompareValue;
      }
    }
  }
}


В открытом интерфейсе несколько методов:
Add<P>(Expression<Func<T, P>> expression
Добавляет выражение, которое будет учавствовать в сравнении со стратегией сравнения по-умолчанию (Подробности ниже)
Add<P>(Expression<Func<T, P>> expression, 
  IEqualityComparer<P> equality, 
  IComparer<P> comparison)
Добавляет выражение, которое будет учавствовать в сравнении с использованием указанных компараторов. Можно в значении какого-либо компаратора указать null и тогда будет стратегия сравнения по-умолчанию.
AddDefault<P>(Expression<Func<T, P>> expression)
Добавляет выражение, которое будет учавствовать в сравнении с использованием компараторов по-умолчанию: EqualityComparer<P>.Default и Comparer<P>.Default. Отличается от стратегии сравнения по-умолчанию!
Add<P, C>(Expression<Func<T, P>> expression, C comparer) 
  where C : IEqualityComparer<P>, IComparer<P>
Добавляет выражение, которое будет учавствовать в сравнении с использованием указанного компаратора, который одновременно может сравнивать объекты на равенство и на отношение. Удоебен для сравнения строк: StringComparer как раз попадает под указанные ограничения.
AddEquality<P>(Expression<Func<T, P>> expression, 
  IEqualityComparer<P> equality = null)
Добавляет выражение, которое будет учавствовать только в сравнении на равенство с использованием указанного компаратора. Если значение компаратора null, то будет использована стратегия по-умолчанию.
AddComparison<P>(Expression<Func<T, P>> expression, 
  IComparer<P> comparison = null)
Добавляет выражение, которое будет учавствовать только в сравнении отношения с использованием указанного компаратора. Если значение компаратора null, то будет использована стратегия по-умолчанию.
Стратегия по умолчанию не использует дефолтовые компараторы (которые можно передать в методы явно или подключить вызовом AddDefault).

Создаются компараторы из билдера следующими вызовами:
EqualityComparer<T> ToEqualityComparer()
Компаратор, который может использоваться для сравнения объектов на равенство
Comparer<T> ToComparer()
Компаратор, который может использоваться для сравнения объектов на отношение ("больше-меньше")
Так же стоит заметить, что в качестве выражений, по которым стоятся компараторы совсе не обязательно указывать MemberExpression. Можно указать вообще любое выражение. Например:
    var builder = new ComparerBuilder<X>()
      .Add(x => x.A + 3)
      .Add(x => x.B.Trim(), StringComparer.OrdinalIgnoreCase)
      .AddDefault(x => x.C != null ? x.C : 42);


Реализация на третьем фреймворке и третьем шарпе


Всвязи с тем, что в третьем фреймворке функциональность expressions сильно ограничена, поддерживается только создание EqualityComparer-а. Так же в качестве expression возможно указание только MemberExpression.

  Сам класс билдера
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace GBricks.Collections
{
  public class ComparerBuilder<T>
  {
    private static readonly ConstantExpression Null = Expression.Constant(null);
    private static readonly ConstantExpression Zero = Expression.Constant(0);

    private static readonly ParameterExpression Left = Expression.Parameter(typeof(T), null);
    private static readonly ParameterExpression Right = Expression.Parameter(typeof(T), null);

    private static readonly Func<object, object, bool> EqualsDelegate = Object.Equals;
    private static readonly Func<int> GetHashCodeDelegate = new object().GetHashCode;

    private static readonly Type ObjectType = typeof(object);
    private static readonly bool IsValueType = typeof(T).IsValueType;

    public ComparerBuilder() {
      EqualsExpressions = new List<Expression>();
      GetHashCodeExpressions = new List<Expression>();
    }

    private IList<Expression> EqualsExpressions { get; set; }
    private IList<Expression> GetHashCodeExpressions { get; set; }

    #region Expression Helpers

    private static BinaryExpression ReferenceEqual(Expression left, Expression right) {
      return Expression.Equal(Expression.Convert(left, ObjectType), Expression.Convert(right, ObjectType));
    }

    private static BinaryExpression ReferenceNotEqual(Expression left, Expression right) {
      return Expression.NotEqual(Expression.Convert(left, ObjectType), Expression.Convert(right, ObjectType));
    }

    private static BinaryExpression IsNull(Expression value) {
      return ReferenceEqual(value, Null);
    }

    private static BinaryExpression IsNotNull(Expression value) {
      return ReferenceNotEqual(value, Null);
    }

    private static MemberExpression Update(MemberExpression member, Expression expression) {
      if(member == null) {
        throw new ArgumentNullException("member");
      }//if

      return Expression.MakeMemberAccess(expression, member.Member);
    }

    #endregion Expression Helpers

    protected virtual Expression MakeEquals<P>(Expression left, Expression right, IEqualityComparer<P> comparer) {
      if(left == null) {
        throw new ArgumentNullException("left");
      } else if(right == null) {
        throw new ArgumentNullException("right");
      }//if

      if(comparer != null) {
        Func<P, P, bool> @delegate = comparer.Equals;
        var instance = Expression.Constant(comparer);
        return Expression.Call(instance, @delegate.Method, left, right);
      } else {
        if(typeof(P).IsValueType) {
          return Expression.Equal(left, right);
        } else {
          return Expression.Call(EqualsDelegate.Method, left, right);
        }//if
      }//if
    }

    protected virtual Expression MakeGetHashCode<P>(Expression value, IEqualityComparer<P> comparer) {
      if(value == null) {
        throw new ArgumentNullException("value");
      }//if

      if(comparer != null) {
        Func<P, int> @delegate = comparer.GetHashCode;
        var instance = Expression.Constant(comparer);
        return Expression.Call(instance, @delegate.Method, value);
      } else {
        var expression = Expression.Call(value, GetHashCodeDelegate.Method);
        if(typeof(P).IsValueType) {
          return expression;
        } else {
          return Expression.Condition(IsNull(value), Zero, expression);
        }//if
      }//if
    }

    private ComparerBuilder<T> AddCore<P>(Expression<Func<T, P>> expression, IEqualityComparer<P> comparer) {
      if(expression == null) {
        throw new ArgumentNullException("expression");
      } else if(!(expression.Body is MemberExpression)) {
        throw new ArgumentException("Should be a MemberExpression", "expression");
      }//if

      var member = (MemberExpression)expression.Body;

      var left = Update(member, Left);
      var right = Update(member, Right);

      var equals = MakeEquals(left, right, comparer);
      EqualsExpressions.Add(equals);

      var hash = MakeGetHashCode(left, comparer);
      GetHashCodeExpressions.Add(hash);

      return this;
    }

    public ComparerBuilder<T> Add<P>(Expression<Func<T, P>> expression, IEqualityComparer<P> comparer) {
      return AddCore(expression, comparer ?? EqualityComparer<P>.Default);
    }

    public ComparerBuilder<T> Add<P>(Expression<Func<T, P>> expression) {
      return AddCore(expression, null);
    }

    private static Expression<Func<T, T, bool>> BuildEquals(IEnumerable<Expression> items) {
      if(items == null) {
        throw new ArgumentNullException("items");
      }//if

      var expression = items.Aggregate(Expression.AndAlso);
      var body = IsValueType
        ? expression
        //: (object)x == (object)y || ((object)x != null && (object)y != null && expression);
        : Expression.OrElse(
            ReferenceEqual(Left, Right),
            Expression.AndAlso(
              Expression.AndAlso(IsNotNull(Left), IsNotNull(Right)),
              expression));
      return Expression.Lambda<Func<T, T, bool>>(body, Left, Right);
    }

    private static Expression<Func<T, int>> BuildGetHashCode(IEnumerable<Expression> items) {
      if(items == null) {
        throw new ArgumentNullException("items");
      }//if

      var expression = items.Aggregate(Expression.ExclusiveOr);
      var body = IsValueType
        ? expression
        //: ((object)x == null) ? 0 : expression;
        : Expression.Condition(IsNull(Left), Zero, expression);
      return Expression.Lambda<Func<T, int>>(body, Left);
    }

    public EqualityComparer<T> ToEqualityComparer() {
      if(EqualsExpressions.Count == 0 || GetHashCodeExpressions.Count == 0) {
        return ConstEqualityComparer.Default;
      }//if
      Debug.Assert(EqualsExpressions.Count == GetHashCodeExpressions.Count);

      var equals = BuildEquals(EqualsExpressions);
      var hash = BuildGetHashCode(GetHashCodeExpressions);
      return new MethodEqualityComparer(equals.Compile(), hash.Compile());
    }

    [Serializable]
    private sealed class MethodEqualityComparer : EqualityComparer<T>
    {
      public MethodEqualityComparer(Func<T, T, bool> equals, Func<T, int> hash) {
        if(equals == null) {
          throw new ArgumentNullException("equals");
        } else if(hash == null) {
          throw new ArgumentNullException("hash");
        }//if

        EqualsMethod = equals;
        GetHashCodeMethod = hash;
      }

      private Func<T, T, bool> EqualsMethod { get; set; }

      private Func<T, int> GetHashCodeMethod { get; set; }

      public override bool Equals(T x, T y) {
        return EqualsMethod(x, y);
      }

      public override int GetHashCode(T obj) {
        return GetHashCodeMethod(obj);
      }
    }

    [Serializable]
    private sealed class ConstEqualityComparer : EqualityComparer<T>
    {
      private static readonly EqualityComparer<T> @default = new ConstEqualityComparer(true, 0);

      public ConstEqualityComparer(bool equals, int hash) {
        EqualsValue = equals;
        GetHashCodeValue = hash;
      }

      public static new EqualityComparer<T> Default {
        [DebuggerStepThrough]
        get { return @default; }
      }

      private bool EqualsValue { get; set; }

      private int GetHashCodeValue { get; set; }

      public override bool Equals(T x, T y) {
        return EqualsValue;
      }

      public override int GetHashCode(T obj) {
        return GetHashCodeValue;
      }
    }
  }
}


Методов всего ничего:
Add<P>(Expression<Func<T, P>> expression
Добавляет выражение, которое будет учавствовать в сравнении со стратегией сравнения по-умолчанию (Подробности в предыдущем разделе)
Add<P>(Expression<Func<T, P>> expression, 
  IEqualityComparer<P> comparer)
обавляет выражение, которое будет учавствовать только в сравнении на равенство с использованием указанного компаратора. Если значение компаратора null, то будет использована стратегия по-умолчанию.
EqualityComparer<T> ToEqualityComparer()
Создаёт компаратор, который может использоваться для сравнения объектов на равенство
С вопросами, коментариями и предложениями — велкам
Help will always be given at Hogwarts to those who ask for it.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.