Cómo comparar objetos en C#

Comparar objetos es una acción muy común en cualquier programa que estemos escribiendo, de hecho, posiblemente sea una de las acciones que con mas frecuencia realizemos, ya que cada vez que indicamos una expresión if o un swich estamos comparando.

Pero comparar no termina ahí, si estamos filtrando u ordenando también vamos a comparar dos elementos.

 

 

1 - Diferencia entre == y equals en C#

Cuando comparamos lo hacemos o bien con == o con el método Equals de objeto y pese a que parece que ambas opciones van a darnos el mismo resultado no es así.

  • Nota: tenemos otras opciones como utilizar Object.Equals(elemento1, elemento2) o Object.ReferenceEquals(e1, e2);.

 

El resultado que vamos a obtener va a depender si estamos trabajando con tipos por valor o tipos por referencia.

Ademas, recuerda que puedes sobreescribir los operadores, en este ejemplo lo vamos a ver todo sin sobreescribir

 

1.1 - Comparar tipos por valor

Cuando utilizamos tipos por valor, osea tipos primitivos o structs si utilizamos el operador == lo que vamos a hacer es comparar el valor del elemento y nos devolverá verdadero o falso.

int value1 = 1;
int value2 = 1;

bool sonIguales = value1 == value2; //true

 

Ten en cuenta que si creas un struct, por defecto no vas a poder compararlos con el operador==, sino que tienes que sobreescribir dicho operador.

public struct ElementoPorValor 
{
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
  
    public static bool operator ==(ElementoPorValor c1, ElementoPorValor c2) 
    {
        return c1.Equals(c2);
    }
    
    public static bool operator !=(ElementoPorValor c1, ElementoPorValor c2) 
    {
        return !c1.Equals(c2);
    }
}

Ahora ya podemos ejecutar el operador ==, pero como te has dado cuenta, estamos llamando al método .Equals que en los tipos por valor, comprobará primero el tipo, y luego comprueba que cada uno de los valores son iguales.

ElementoPorValor elemento1 = new ElementoPorValor()
{
    Valor1 = 1,
    Valor2 = 2
};

ElementoPorValor elemento2 = new ElementoPorValor()
{
    Valor1 = 1,
    Valor2 = 2
};

bool sonIguales = elemento1 == elemento2; //true
bool sonIgualesOpt2 = elemento1.Equals(elemento2);//true

 

1.2 - Tipos por referencia

En los tipos por referencia tanto el operador == como el método .Equals comprueban que la referencia al objeto sea la misa:

ElementoReferencia elemento1 = new ElementoReferencia()
{
    Valor1 = 1,
    Valor2 = 2
};
ElementoReferencia elemento2 = new ElementoReferencia()
{
    Valor1 = 1,
    Valor2 = 2
};

ElementoReferencia elemento1Copia = elemento1;

bool dosPrimeros = elemento1 == elemento2; //false;
bool laCopia = elemento1 == elemento1Copia; //true


bool dosPrimeros2 = elemento1.Equals(elemento2); //false;
bool laCopia2 = elemento1.Equals(elemento1Copia); //true

 

Por supuesto como en el caso anterior también podemos sobreescribir el operador == para que funcione como nosotros queramos:

public class ElementoReferencia 
{
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
    
    public static bool operator ==(ElementoReferencia c1, ElementoReferencia c2)
    {
        return c1.Valor1 == c2.Valor1 && c1.Valor2 == c2.Valor2;
    }
    
    public static bool operator !=(ElementoReferencia c1, ElementoReferencia c2)
    {
        return c1.Valor1 != c2.Valor1 || c1.Valor2 != c2.Valor2;
    }
}

//Y ahora esta comparación 
bool dosPrimeros = elemento1 == elemento2; //anteriormente falso ahora da verdadero

 

 

2 - La interfaz IEquatable

Hemos visto las formas más comunes de comparar dos objetos, pero, que pasa si nuestro caso de uso es un poco más extremo. Por ejemplo, nuestro objeto tiene 3 propiedades pero por el motivo que sea solo queremos comprobar 2 de ellas.

public class ElementoEspecial
{
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
    public DateTime Fecha { get; set; }
}

 

Como hemos visto anteriormente podemos sobreescribir el operador ==, pero personalmente no recomiendo quedarnos únicamente ahí. 

 

Lo que yo personalmente recomiendo es implementar la interfaz IEquatable<T> la cual es utilizada por el tipo Object, con lo cual lo único que haremos será sobrescribir el funcionamiento del método Equals

 

Cabe destacar antes de continuar que Equals actúa sobre recibe object  bool Equals(object obj) y que para añadir el nuestro, lo podemos hacer sin utilizar IEquatable ya que crearemos bool Equals(T objeto) pero, por compresión de quien venga después en el código, deberíamos sobreescribir IEquatable.

 

De esta manera, modificando tanto el operador == como el Equals se nos queda un código claro y si además invocamos .Equals desde el operador== lo que haremos será crear una consistencia en la ejecución muy importante, la cual nos puede sacar de más de uno y dos problemas.

public class ElementoIEquatable : IEquatable<ElementoIEquatable>
{
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
    public DateTime Fecha { get; set; }

    public static bool operator ==(ElementoIEquatable c1, ElementoIEquatable c2) 
    {
        return c1.Equals(c2);
    }
    
    public static bool operator !=(ElementoIEquatable c1, ElementoIEquatable c2) 
    {
        return !c1.Equals(c2);
    }
    
    //Comparamos los objetos, pero sin tener en cuenta la fecha.
    public bool Equals(ElementoIEquatable other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Valor1 == other.Valor1 && Valor2 == other.Valor2;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((ElementoIEquatable)obj);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Valor1, Valor2);
    }
}

 

 

3 - Las Interfaz IComparer e IComparable

Si lo que buscamos es comparar objetos de una forma en la que un elemento es mayor que otro no lo haremos con IEQuatable, porque como hemos visto, solo nos sale un resultado verdadero o falso. 

Por el contrario lo haremos con la interfaz ICompare o la interfaz IComparable, ambas parecen lo mismo pero son un poquitín diferentes.

 

Primero vamos con las partes comunes, el resultado, ambas van a crear un método el cual va a devolver un entero, y este entero tiene 3 posibilidades:

  • -1 si el primer elemento, o el actual es “mayor”.
  • 0 si ambos son iguales
  • 1 si el segundo elemento, o el introducido es “mayor

La definición de "mayor" es la que tu quieras diseñar dentro del método.

 

Cuando implementamos la interfaz IComparable<T> vamos a tener que implementar  el método CompareTo(T obj) y cuando implementamos IComparer<T> vamos a  implementar Compare(T x, T y).

public class ElementoIComparer : IComparer<ElementoIComparer>, IComparable<ElementoIComparer>
{  
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
    //ignoramos la fecha de la comparación
    public DateTime Fecha { get; set; }


    public int Compare(ElementoIComparer x, ElementoIComparer y)
    {
        if (ReferenceEquals(x, y)) return 0;
        if (ReferenceEquals(null, y)) return 1;
        if (ReferenceEquals(null, x)) return -1;
        int valor1Comparison = x.Valor1.CompareTo(y.Valor1);
        if (valor1Comparison != 0) return valor1Comparison;
        return x.Valor2.CompareTo(y.Valor2);
    }

    public int CompareTo(ElementoIComparer other)
    {
        if (ReferenceEquals(this, other)) return 0;
        if (ReferenceEquals(null, other)) return 1;
        int valor1Comparison = Valor1.CompareTo(other.Valor1);
        if (valor1Comparison != 0) return valor1Comparison;
        return Valor2.CompareTo(other.Valor2);
    }
}

Como puedes ver en ambos casos estamos ignorando la fecha de la comparación

 

3.1 - Cuándo utilizar IComparable

Implementar icomparable nos va a permitir que si tenemos una lista de elementos podemos ordenarla utilizando linq, basándonos en la lógica que tenemos dentro del método CompareTo

ElementoIComparar elemento1 = new ElementoIComparar(1, 2, DateTime.UtcNow);
ElementoIComparar elemento2 = new ElementoIComparar(2, 2, DateTime.UtcNow); 
ElementoIComparar elemento3 = new ElementoIComparar(1, 2, DateTime.UtcNow);


List<ElementoIComparar>listaComparar = new List<ElementoIComparar>()
{
    elemento1, elemento2, elemento3
};

List<ElementoIComparar> listaOrdenada = listaComparar.OrderBy(x => x).ToList();

// elemento2 es el último al tener el primer valor con un 2
Assert.AreEqual(elemento2, listaOrdenada.Last());

Como vemos el elemento2 ha pasado a la última posición.

 

3.2 - Cuándo Utilizar IComparer

Cuando quieres realizar una comparación, pero no necesariamente con el objeto en el que estás. Me explico, implementar la interfaz IComparer<T> nos crea el método Compare(T x, T y) con lo cual puedes introducir Cualquier T, no estás comparando con el elemento en concreto. 

Personalmente pienso que esta comparación debería estar en una clase abstracta, helper etc, porque pese a ser ejecutada en un objeto, no interacciona con el.

 

 

Conclusión

  • En este post hemos visto cómo comparar objetos en C# utilizando diferentes métodos.
  • Cómo modificar el operador== y el método equals para poder alterar las comparaciones
  • Cómo implementar las interfaces IComparable y IComparer.

 


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café