Zastosowanie LambdaEqualityComparer

Jakiś czas temu Maciej Aniserowicz na swoim blogu opublikował kod klasy LambdaEqualityComarer

Bardzo często chcielibyśmy aby równość elementów jakiejś klasy zależała tylko od jakiegoś pola (najczęściej od ID), aby to osiągnąć należy nadpisać metody GetHashcode i Equals tej klasy jest to jednak praca dosyć żmudna i powtarzalna, dodatkowo okazuje się że kod ten należy często pisać ponieważ wiele klas wywołuje te metody w celu porównania elementów, przykładem takich klas są kontrolki WPF zawierające Listy jak np. ComboBox. Aby pracę tą trochę skrócić można użyć następującego fragmentu kodu:

    class User
    {
        /// <summary>
        /// Identyfikator
        /// </summary>
        public int ID { get; set; }

        // Inne pola

        private LambdaEqualityComparer<User, int> _comparer = new LambdaEqualityComparer<User, int>(u => u.ID);

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

        public override bool Equals(object obj)
        {
            return _comparer.Equals(this, obj as User);
        }
    }

Aby ten kod działał w 100% poprawnie należy jeszcze zmodyfikować kod Maćka tak aby uwzględniała fakt iż któryś z parametrów metody Equals może być nullem.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            #endregion Private members 

            #region Constructors 
            public LambdaEqualityComparer(Func<T, TValue> converter) 
            { 
                this.converter = converter; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         return converter(x).Equals(converter(y)); 
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                { 
                        return converter(obj).GetHashCode(); 
                 }
            }
            #endregion 
        } 

Na koniec dodam że implementację tą można rozbudować zgodnie z wzorcem dekoratora, co daje nam możliwość wykorzystania istniejących implementacji IEqualityComparer'a.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            private IEqualityComparer<TValue> realComparer;
            #endregion Private members 

            #region Constructors 
           public LambdaEqualityComparer(Func<T, TValue> converter) : this(converter, null) { }

            public LambdaEqualityComparer(Func<T, TValue> converter, IEqualityComarer<TValue> realComparer) 
            { 
                this.converter = converter; 
                this.realComparer = realComparer; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         if  (realComparer == null)
                         {
                                  return converter(x).Equals(converter(y)); 
                          }
                          else
                          {
                                   return realComparer.Equals(converter(x), converter(y));
                           }
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                {
                       if (realComparer == null)
                       { 
                              return converter(obj).GetHashCode(); 
                        }
                         else
                         {
                                  return realComparer.GetHashCode(converter(obj));
                          }
                 }
            }
            #endregion 
        }