How to Compare Objects in C#

Comparing objects is a very common action in any program we write; in fact, it is likely one of the actions we perform most frequently, since every time we use an if expression or a switch we are comparing.

But comparison doesn't stop there. If we're filtering or ordering, we'll also compare two elements.

 

 

1 - Difference between == and equals in C#

When we compare, we either use == or the equals method, and even though both may seem to give the same result, this is not the case.

  • Note: We have other options, such as using Object.Equals(element1, element2) or Object.ReferenceEquals(e1, e2);.

 

The result will depend on whether we are working with value types or reference types.

Also, remember that you can overwrite operators, but in this example, we'll cover everything without overwriting.

 

1.1 - Comparing value types

When using value types, that is, primitive types or structs, if we use the == operator, what we do is compare the value of the element, returning true or false.

int value1 = 1;
int value2 = 1;

bool sonIguales = value1 == value2; //true

 

Keep in mind that if you create a struct, by default you will not be able to compare them using the == operator: you must override this operator.

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);
    }
}

Now we can use the == operator, but as you've noticed, we're calling the .Equals method, which for value types first checks the type, then checks that each value is equal.

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 - Reference types

With reference types, both the == operator and the .Equals method check if the object reference is the same:

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

 

Of course, as in the previous case, we can also override the == operator to make it work as we want:

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;
    }
}

//And now this comparison
bool dosPrimeros = elemento1 == elemento2; //previously false, now returns true

 

 

2 - The IEquatable Interface

We've seen the most common ways to compare two objects, but what if our use case is a bit more specific? For example, our object has 3 properties but for some reason we only want to compare two of them.

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

 

As we've seen before, we can override the == operator, but I personally don't recommend stopping there only. 

 

What I recommend is implementing the IEquatable<T> interface, which is used by the Object type. This way, we simply override the behavior of the equals method. 

 

Before continuing, note that equals acts over object bool Equals(object obj) and to add ours, we can do so without using IEquatable by creating bool Equals(T object), but to make the code clearer for future developers, you should override IEquatable.

 

This way, by modifying both the == operator and equals, the code is clear, and if you also invoke .Equals from the == operator, you create important consistency in execution, which can help you avoid more than one problem.

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);
    }
    
    //Compare objects but ignore the date.
    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 - The IComparer and IComparable Interfaces

If what we want is to compare objects in a way where one element is greater than another, we won't do this with IEquatable, because, as we've seen, only true or false is returned. 

Instead, we'll use the IComparer or IComparable interface. Both seem the same but are slightly different.

 

First, let's go with the common aspects: the result. Both will create a method that returns an integer, and this integer has 3 possibilities:

  • -1 if the first element, or the current one, is considered "greater".
  • 0 if both are equal
  • 1 if the second element, or the one provided, is considered "greater"

How you define "greater" is up to the logic you want to design in the method.

 

When implementing the IComparable<T> interface, you need to implement the CompareTo(T obj) method, and when implementing IComparer<T>, you implement Compare(T x, T y).

public class ElementoIComparer : IComparer<ElementoIComparer>, IComparable<ElementoIComparer>
{  
    public int Valor1 { get; set; }
    public int Valor2 {  get; set; }
    //ignore the date in the comparison
    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);
    }
}

As you can see, in both cases we are ignoring the date in the comparison.

 

3.1 - When to use IComparable

Implementing icomparable allows us, if we have a list of elements, to sort it using linq, based on the logic we have in the CompareTo method.

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 is the last one as the first value has 2
Assert.AreEqual(elemento2, listaOrdenada.Last());

As we see, elemento2 has moved to the last position.

 

3.2 - When to Use IComparer

When you want to make a comparison, but not necessarily with the current object. Let me explain: implementing the IComparer<T> interface creates the Compare(T x, T y) method, meaning you can input any T; you're not comparing with a specific element. 

Personally, I think this comparison should be placed in an abstract class, helper, etc., because even though it is executed in an object, it doesn't interact with it.

 

 

Conclusion

  • In this post we've seen how to compare objects in C# using different methods.
  • How to modify the == operator and the equals method to change comparisons.
  • How to implement the IComparable and IComparer interfaces.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

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 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café