Share via


Directrices para invalidar Equals() y el operador == (Guía de programación de C#)

Actualización: noviembre 2007

En C#, hay dos tipos diferentes de igualdad: igualdad de referencia e igualdad de valor. La igualdad de valor es el significado de igualdad más conocido: significa que dos objetos contienen los mismos valores. Por ejemplo, dos enteros con el valor 2 tienen igualdad de valor. La igualdad de referencia significa que no hay dos objetos que se puedan comparar. Más bien, hay dos referencias de objeto y ambas se refieren al mismo objeto. Esto puede suceder mediante una asignación simple, como se muestra en el ejemplo siguiente:

System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

En este código, sólo existe un objeto, pero hay varias referencias a ese objeto: a y b. Como ambas hacen referencia al mismo objeto, tienen la igualdad de referencia. Si dos objetos tienen igualdad de referencia, también tienen igualdad de valor, pero la igualdad de valor no garantiza la igualdad de referencia.

Para comprobar la igualdad de referencia, utilice ReferenceEquals. Para comprobar la igualdad de valor, utilice Equals.

Reemplazar Equals

Dado que Equals es un método virtual, cualquier clase puede invalidar su implementación. Toda clase que represente un valor (esencialmente cualquier tipo de valor) o un conjunto de valores como grupo (por ejemplo, una clase de números complejos), debe invalidar Equals. Si el tipo implementa IComparable, deberá invalidar Equals.

La nueva implementación de Equals debe seguir todas las garantías de Equals:

  • x.Equals(x) devuelve true.

  • x. Equals (y) devuelve el mismo valor que y. Equals (x).

  • si (x. Equals (y) && y. Equals (z)) devuelve true, x. Equals (z) devuelve true.

  • Las invocaciones sucesivas de x. Equals (y) devuelven el mismo valor siempre que no se modifiquen los objetos a los que hacen referencia x e y.

  • x. Equals (null) devuelve false (sólo para los tipos de valor que no aceptan valores null. Para obtener más información, vea Tipos que aceptan valores NULL (Guía de programación de C#).)

La nueva implementación de Equals no debe producir excepciones. Se recomienda que toda clase que invalide Equals también invalide Object.GetHashCode. También se recomienda que además de implementar Equals (objeto), cualquier clase implemente también Equals (tipo) para su propio tipo con el fin de mejorar el rendimiento. Por ejemplo:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Cualquier clase derivada que puede llamar a Equals en la clase base debe hacerlo antes de finalizar su comparación. En el siguiente ejemplo, Equals llama a la clase base Equals, la cual busca un parámetro null y compara el tipo del parámetro con el tipo de la clase derivada. Esto deja a la implementación de Equals en la clase derivada la tarea de comprobar el nuevo campo de datos declarado en la clase derivada:

class ThreeDPoint : TwoDPoint
{
    public readonly int z;

    public ThreeDPoint(int x, int y, int z)
        : base(x, y)
    {
        this.z = z;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter cannot be cast to ThreeDPoint return false:
        ThreeDPoint p = obj as ThreeDPoint;
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return base.Equals(obj) && z == p.z;
    }

    public bool Equals(ThreeDPoint p)
    {
        // Return true if the fields match:
        return base.Equals((TwoDPoint)p) && z == p.z;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode() ^ z;
    }
}

Reemplazar el operador ==

De forma predeterminada, el operador == comprueba la igualdad de referencia determinando si dos referencias indican el mismo objeto. Por consiguiente, los tipos de referencia no tienen que implementar el operador == para obtener esta funcionalidad. Cuando un tipo es inmutable, es decir, los datos que contiene la instancia no pueden cambiarse, la sobrecarga del operador == para comparar la igualdad de valor en lugar de la igualdad de referencia puede ser útil porque, como objetos inmutables, se pueden considerar iguales siempre y cuando tengan el mismo valor. No se recomienda invalidar el operador == en tipos no inmutables.

Las implementaciones del operador sobrecargado == no deben producir excepciones. Cualquier tipo que sobrecargue el operador == también debe sobrecargar el operador !=. Por ejemplo:

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}
Nota:

Un error común en las sobrecargas del operador == es utilizar (a == b), (a == null) o (b == null) para comprobar la igualdad de referencia. Por el contrario, esto crea una llamada al operador sobrecargado ==, lo que origina un bucle infinito. Utilice ReferenceEquals o convierta el tipo a Object, para evitar el bucle.

Vea también

Conceptos

Guía de programación de C#

Referencia

Instrucciones, expresiones y operadores (Guía de programación de C#)

Operadores (Guía de programación de C#)

Operadores sobrecargables (Guía de programación de C#)

Operador == (Referencia de C#)

Operador () (Referencia de C#)