miércoles, 16 de noviembre de 2016

Generic IEqualityComparer -E-





This is my first post in English. I’ll try to post in English and Spanish.

In this post I’ll talk of a custom Generic IEqualityComparer implementation.

IEqualityComparer is a very important interface for comparer tasks in the LinQ world.­­ The next extended methods have an overload with this parameter type: Contains, Distinct, Except, Intersect, GrouBy, GroupJoin, Join, SecuenceEqual, ToDictionary, ToLookUp and Union.









In the LinQ world, generate the IEqualityComparer interface, is a tediously task and we don’t have sufficient time. Our generic class come for make things easier for ourselves.

  • Classic IEqualityComparer Implementation (for fields)
  • Generic IEqualityComparer Implementation (for fields)
  • Classic IEqualityComparer Implementation (for expression)
  • Generic IEqualityComparer Implementation (for Expression)
  • Code GenericIEqualityComparer class


The code of this example, is available in github.




Classic IEqualityComparer Implementation (for fields)

This example generate the different customers of a sequence.

Class Customer:


public class Customer
{
    public int     ID       { get; set; }
    public string  Name     { get; set; }
    public decimal Sales    { get; set; }
    public string  City     { get; set; }
 
 
 
    public static IEnumerable<Customer> GetData()
    {
        return new List<Customer>()
        {
            new Customer { ID = 1, Name = "Philips"   , Sales = 2000000m, City = "Madrid"   },
            new Customer { ID = 2, Name = "Pionner"   , Sales = 1000000m, City = "Berlin"   },
            new Customer { ID = 3, Name = "Renault"   , Sales = 2000000m, City = "Paris"    },
            new Customer { ID = 4, Name = "Sony Music", Sales =  500000m, City = "London"   },
            new Customer { ID = 5, Name = "Sony SCEE" , Sales = 2000000m, City = "Tokio"    },
            new Customer { ID = 6, Name = "Pepsi"     , Sales = 9000000m, City = "New York" },
            new Customer { ID = 6, Name = "LG"        , Sales = 2000000m, City = "Rome"     }
        };
    }
}


Note

Look, 2 last customers have same ID.













If we call a Distinct LinQ Extension Method, the result would be a new sequence with 7 elements, would not found any distinct member. This is because Distinct compare instances for default, and all are different.


using System;
using System.Linq;
using DAL;
using static System.Console;
 
 
namespace ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var customers = Customer.GetData();
 
            WriteLine($"There are {customers.Count()} customers.");
 
            var customersDifferents = customers.Distinct();
 
            WriteLine($"There are {customersDifferents.Count()} DIFFERENT customers.");
 
            Console.Read();
        }
    }
}





Now, we think a field or fields that differentiate our class. ID for example.  IEqualityComparer helps you to manage and improve the efficiency:


public class CustomerForIDEqualityComparer : IEqualityComparer<Customer>
{
    public bool Equals(Customer x, Customer y)
    {
        bool result = x.ID == y.ID;
 
        return result;
    }
 
    public int GetHashCode(Customer obj)
    {
        return obj.ID.GetHashCode();
    }
}



For more information of IEqualityComparer classic implementation: Spanish - English

The IEqualityComparer class name tell us your purpose, different customers for a field ID. Could be more IEqualityComparer classes for different objective and different fields (ForCity, ForSales, etc), as we needed.

We use our IEqualityComparer class:


static void Main(string[] args)
{
    var customers = Customer.GetData();
 
    WriteLine($"There are {customers.Count()} customers.");
 
    var customersDifferents = customers.Distinct(new CustomerForIDEqualityComparer()).ToList();
 
    WriteLine($"There are {customersDifferents.Count()} DIFFERENT customers.");
 
    Console.Read();
}











Good work, the result is fine because there are 2 customers with the same ID.




Generic IEqualityComparer Implementation (for fields)

In this moment, we’ll donloaded the GenericEqualityComparer in nuget.

We have 2 posibility:

Through menu AddReference


:







































Or by Package Manager Console:











































Install-Package MoralesLarios.GenericEqualityComparer

















The namespace for the class is:


using MoralesLarios.Generics;


We will make previous example, but we’ll use GenericEqualityComparer class. Remember this class isn’t only suitable for any customer field or any customer field’s combination also for any field class or any field’s combination of any class.

GenericEqualityComparer class has an overload with a Func<T, object> parameter, this type is the same that Select method (System.LinQ).

Example:


static void Main(string[] args)
{
    var customers = Customer.GetData();
            
    // Field Generic Comparator
    Func<Customer, object> fieldComparator = customer => customer.ID;
 
    // Instanct GenericIEqualityComparerClass
    GenericEqualityComparer<Customer> genericCustomerIEqualityComparer 
        = new GenericEqualityComparer<Customer>(fieldComparator);
 
    WriteLine($"There are {customers.Count()} customers.");
 
    var customersDifferents = customers.Distinct(genericCustomerIEqualityComparer).ToList();
 
    WriteLine($"There are {customersDifferents.Count()} DIFFERENT customers.");
 
    Console.Read();
}


The first step is create the FieldComparator (Func<T, object>). With this variable we tell Field(s) is/are our virtual PK.

The second step is an instantiate the GenericEqualityComparer class. Your TypeParameter is Customer and your constructor parameter is the FieldComparator.

Result:











The same result, but it’s much readable.


The same code, but generic version, it is code online:
































Classic IEqualityComparer Implementation (for expression)

We’ll write a classic implementation of IEqualityComparer class. In our example, we need compare customers. Two customers are distinct if your first char Name is different.

This is our IEqualityComparer class for expression:


public class Customer1stCharNameComparer : IEqualityComparer<Customer>
{
    public bool Equals(Customer x, Customer y)
    {
        bool result = x.Name?[0] == y.Name?[0];
 
        return result;
    }
 
    public int GetHashCode(Customer obj)
    {
        return obj.Name == null ? 0 : obj.Name[0].GetHashCode();
    }
}


We do the same example, but with the IEqualityComparer for expression now:


static void Main(string[] args)
{
    var customers = Customer.GetData();
 
    WriteLine($"There are {customers.Count()} customers.");
 
    var customersDifferents = customers.Distinct(new Customer1stCharNameComparer()).ToList();
 
    WriteLine($"There are {customersDifferents.Count()} DIFFERENT customers.");
 
    Console.Read();
}


















Result:












Four different customers start with: P, R, S and L.




Generic IEqualityComparer Implementation (for expression)

For Expression, is a broader scope, and doesn’t have limits.

GenericEqualityComparer has an overload with a Func<T, T, bool>, this parameter show the comparison expression.

This is the same previous example, with GenericEqualityComparer:


static void Main(string[] args)
{
    var customers = Customer.GetData();
 
    // Field Generic Comparator
    Func<Customer, Customer, bool> expressionComparator = (customer1, customer2) => customer1.Name?[0] == customer2.Name?[0];
 
    // Instanct GenericIEqualityComparerClass
    GenericEqualityComparer<Customer> genericCustomerIEqualityComparer
        = new GenericEqualityComparer<Customer>(expressionComparator);
 
    WriteLine($"There are {customers.Count()} customers.");
 
    var customersDifferents = customers.Distinct(genericCustomerIEqualityComparer).ToList();
 
    WriteLine($"There are {customersDifferents.Count()} DIFFERENT customers.");
 
    Console.Read();
}


The same result:












As in the previous case, the same result, but it’s much readable.

The same code, but generic version, it is code online:






























Code GenericIEqualityComparer class

I’ll try to explain the code of GenericEqualityComparer.

This is the code:


using System;
using System.Collections.Generic;
 
namespace CodComun
{
    public class GenericEqualityComparer<T> : IEqualityComparer<T>
    {
        private Func<T, object>  _virtualFieldComparator;
        private Func<T, T, bool> _virtualFilterComparator;
 
 
        public GenericEqualityComparer(Func<T, object> virtualFieldComparator)
        {
            if (virtualFieldComparator == null) throw new ArgumentNullException(nameof(virtualFieldComparator), $"{nameof(virtualFieldComparator)} doesn't be null");
 
            Reset();
 
            this._virtualFieldComparator = virtualFieldComparator;
        }
 
        public GenericEqualityComparer(Func<T, T, bool> virtualFilterComparator)
        {
            if (virtualFilterComparator == null) throw new ArgumentNullException(nameof(virtualFilterComparator), $"{nameof(virtualFilterComparator)}  doesn't be null");
 
            Reset();
 
            this._virtualFilterComparator = virtualFilterComparator;
        }
 
        private void Reset()
        {
            _virtualFieldComparator  = null;
            _virtualFilterComparator = null;
        }
 
 
 
        public bool Equals(T x, T y)
        {
            bool result = false;
 
            if (_virtualFieldComparator != null) result = _virtualFieldComparator(x).Equals(_virtualFieldComparator(y));
            else result = _virtualFilterComparator(x, y);
 
            return result;
        }
 
        public int GetHashCode(T obj)
        {
            int result = 0;
 
            if (_virtualFieldComparator != null) result = _virtualFieldComparator(obj).GetHashCode();
            else result = _virtualFilterComparator(obj, obj).GetHashCode();
 
            return result;
        }
    }
}


First, this class implements the IEqualityComparer<T>, as the classics implementations.

It has two private fields. This fields store (dependency injection mode) the value for your two constructors’ parameters. Each represent the two modes of class: For Field or For Expression, therefore one will be always null.

It has two constructors, to enabled For Field mode or For Expression mode.

The private method Reset, reset all modes.

Finally, the two public methods, Equals and GetHashCode implementing IEqualityComparer<T>, and use the encapsulate actions of theirs two private fields.


Hope this article will help you to understand the IEqualityComparer<T> interface. Don´t hesitate to write questions of suggestions. 








Thank You very much to Santiago Sánchez García  for your English, your help and your advices.