miércoles, 28 de enero de 2015

IEqualityComparer




Llegado a este punto, vamos a hacer un pequeño parón dentro de los operadores de consulta, para centrarnos en una interface muy útil dentro de la comparación de nuestros objetos, y que bajo mi opinión, mucha gente no le da la importancia que realmente tiene, hasta llegar al punto de que muchos autores ni siquiera le dediquen una línea dentro de libros dedicados por entero a LinQ.

En pocas palabras, la interface IEqualityComparer<T>, nos permite especificar nuestra propia clave de comparación, sin que para ello tengamos que redefinir ninguno de los métodos iniciales de la clase Object. Esto nos ofrece una versatilidad gigante, ya que podemos crear todos los que queramos según nuestras necesidades y nuestro modo de operar. Veremos que esto será clave en el uso de operadores como Group By, Distinct, Union, Intersect, etc.






Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.




Es importante no confundir con la finalidad de la interfaz IComparer<T>, ya que esta proporciona funcionalidad de comparación pero para niveles de clasificación y ordenación.

Para su uso, Microsoft recomienda derivar de la clase EqualityComparer<T> antes de implementar la Interfaz IEqualityComparer<T>, pero no creo que haya mucha diferencia entre el uso de uno y otro, ya que la primera es una clase abstracta con 2 métodos abstractos (que te obliga a redefinir) y la segunda es una interfaz con esos dos mismos métodos, que te obligan a implementar.

Como ejemplo pondré las dos firmas la de la clase y la de la Interface:

public interface IEqualityComparer<in T>
{
    bool Equals(T x, T y);
    int  GetHashCode(T obj);
}

 [Serializable]
public abstract class EqualityComparer<T> : IEqualityComparer, IEqualityComparer<T>
{
    protected EqualityComparer();
    public static EqualityComparer<T> Default { get; }
    public abstract bool Equals(T x, T y);
    public abstract int GetHashCode(T obj);
}


Vamos a centrar en explicar los dos métodos que nos interesan:

Equals             à Acción que determina cuando dos objetos son iguales.

GetHashCode à Devuelve un código Hash para el objeto especificado.

Estos son los métodos que tendremos que redefinir o implementar para crear nuestro comparador personalizado.


Vamos a crear un par de ejemplos para nuestra clase Persona .Antes de nada volvemos a presentarla por si alguno no se la sabe todavía:


public class Persona
{
    public int      Id              { get; set; }
    public string   Nombre          { get; set; }
    public DateTime FechaNacimiento { get; set; }
    public decimal  Ingresos        { get; set; }      
}

Como hemos dicho en la parte inicial, nuestro objetivo es crear una clave de comparación, que facilitaremos a nuestros métodos para indicarle según nuestro criterio, cuando dos objetos del tipo Persona son iguales. 

En nuestro primer ejemplo lo haremos simplemente por el campo Id, que sería el más lógico para este tipo de datos.


class PersonaPorIdEqualityComparer : EqualityComparer<Persona>
{
    public override bool Equals(Persona x, Persona y)
    {
        return x.Id == y.Id;
    }
 
    public override int GetHashCode(Persona obj)
    {
        return obj.Id.GetHashCode();
    }
}

Simplemente hemos creado una clase que hereda de EqualityComparer<Persona> y hemos redefinido sus dos métodos. El método Equals, que recibe 2 parámetros de tipo Persona, que no son más que los 2 objetos que compararemos para saber si son o no son iguales. Y el método GetHashCode, en el que reutilizaremos el mismo método de la clase base Object, de la propiedad utilizada en cuestión.

Así podremos decirle a cualquier método de comparación o creación, que para su uso, nosotros entendemos que una persona es igual a otra si tiene el mismo Id.

Vamos a crear un segundo ejemplo, en el que por necesidades del guion nos tuviéramos que olvidar de los Ids, y para nosotros una persona fuera diferente de otra, siempre que no se llamen igual y hayan nacido en un día diferente.

Vamos a ver el ejemplo:

class PersonaPorNombreFechaEqualityComparer : IEqualityComparer<Persona>
{
    public bool Equals(Persona x, Persona y)
    {
        return x.Nombre          == y.Nombre
            && x.FechaNacimiento == y.FechaNacimiento;
    }
 
    public int GetHashCode(Persona obj)
    {
        return obj.Nombre.GetHashCode() + obj.FechaNacimiento.GetHashCode();
    }
}


Así podríamos tener n formas diferentes para diferenciar los objetos Persona, según nuestras necesidades.

Cabe destacar que este tipo de acciones lo realizaremos sobre objetos ‘compuestos’ con un conjuntos de propiedades. Para estructuras como int, decimal, datetime, etc., esto no será necesario, ya que por defecto comparará por su valor. El mismo comportamiento tendrá para sus equivalentes nulables y la clase inmutable string.


Hemos explicado la forma generarla y no hemos hecho ninguna demostración práctica de cómo aplicarlo en la vida real. Aunque parezca un poco vacío, en las siguientes entregas, estudiaremos muchos operadores que dentro de sus sobrecargas admiten una clase de este tipo y que nos harán apreciar su verdadero valor dentro de LinQ.