C # .Equals (), .ReferenceEquals () y == operador

84

Mi comprensión de estos tres fue:

  • .Equals()pruebas de igualdad de datos (a falta de una mejor descripción). .Equals()puede devolver True para diferentes instancias del mismo objeto, y este es el método que se anula con más frecuencia.

  • .ReferenceEquals() comprueba si dos objetos son la misma instancia y no se pueden anular.

  • ==es el mismo que el ReferenceEquals()predeterminado, pero PUEDE anularse.

Pero la estación C # dice:

En la clase de objeto, los métodos Equalsy ReferenceEqualsson semánticamente equivalentes, excepto que ReferenceEqualsfunciona solo en instancias de objeto. El ReferenceEqualsmétodo es estático.

Ahora no lo entiendo. ¿Alguien puede arrojar algo de luz sobre esto?

999999
fuente
Consulte stackoverflow.com/questions/814878/… y muchas otras preguntas de StackOverflow sobre este tema.
Ian Mercer
@Alto tengo. Es solo la parte que extraje de C # Station lo que me confunde.
999999

Respuestas:

87

La fuente de su confusión parece ser que hay un error tipográfico en el extracto de la estación C #, que debería decir: "... excepto que Equals solo funciona en instancias de objetos. El método ReferenceEquals es estático".


Está ligeramente en lo cierto acerca de las diferencias en los significados semánticos de cada uno (aunque "diferentes instancias del mismo objeto" parece un poco confuso, probablemente debería leer "diferentes instancias del mismo tipo ) y sobre cuáles se pueden anular.

Si dejamos eso de lado, vamos a tratar con la última parte de su pregunta, es decir, cómo funcionan con System.Objectinstancias y System.Objectreferencias simples (necesitamos ambos para esquivar la naturaleza no polimórfica de ==). Aquí, las tres operaciones funcionarán de manera equivalente , pero con una advertencia: Equalsno se puede invocar null.

Equalses un método de instancia que toma un parámetro (que puede ser null). Dado que es un método de instancia (debe invocarse en un objeto real), no se puede invocar en una nullreferencia.

ReferenceEqualses un método estático que toma dos parámetros, cualquiera o ambos pueden ser null. Dado que es estático (no asociado con una instancia de objeto ), no arrojará un NullReferenceExceptionbajo ninguna circunstancia.

==es un operador que, en este caso ( object), se comporta de forma idéntica ReferenceEquals. Tampoco arrojará un NullReferenceException.

Para ilustrar:

object o1 = null;
object o2 = new object();

//Technically, these should read object.ReferenceEquals for clarity, but this is redundant.
ReferenceEquals(o1, o1); //true
ReferenceEquals(o1, o2); //false
ReferenceEquals(o2, o1); //false
ReferenceEquals(o2, o2); //true

o1.Equals(o1); //NullReferenceException
o1.Equals(o2); //NullReferenceException
o2.Equals(o1); //false
o2.Equals(o2); //true
Y yo
fuente
Entonces, ¿el extracto de la estación C # citado anteriormente es incorrecto (especialmente si anulo .Equals())?
999999
1
El extracto dice "en la objectclase" . Creo que te saltaste esa parte. Porque de lo contrario no estaría hablando de anularlo.
Domenic
1
Mi respuesta es solo sobre la objectclase.
Ani
@Ani: su siguiente oración era incorrecta, el método estático puede arrojar NullReferenceException: dado que es estático (no asociado con una instancia de objeto), no arrojará una NullReferenceException bajo ninguna circunstancia.
selvaraj
2
Equalstambién es un método estático en el objectque toma dos parámetros. Entonces uno o ambos pueden ser null.
Weston
20

Eche un vistazo a este artículo de MSDN sobre el tema.

Creo que los puntos pertinentes son:

Para comprobar la igualdad de referencias, utilice ReferenceEquals. Para comprobar la igualdad de valores, utilice Equals o Equals.

De forma predeterminada, el operador == prueba la igualdad de referencias determinando si dos referencias indican el mismo objeto, por lo que los tipos de referencia no necesitan implementar el operador == para obtener esta funcionalidad. Cuando un tipo es inmutable, lo que significa que los datos contenidos en la instancia no se pueden cambiar, sobrecargar el 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 que tengan la mismo valor.

¡Espero que esto ayude!

Alastair Pitts
fuente
6
desafortunadamente, el enlace está muerto. +1 para copiar la información relevante.
Pac0
6

Su comprensión de .ReferenceEquals es correcta.

.Equals verifica la igualdad de datos para tipos de valor y la igualdad de referencia para tipos sin valor (objetos generales).

.Equals se puede anular para que los objetos realicen algún tipo de verificación de igualdad de datos

EDITAR: Además, .ReferenceEquals no se puede usar en tipos de valor (bueno, puede, pero siempre será falso)

Luke Schafer
fuente
3

Quiero agregar mis cinco centavos sobre la comparación con "nulo".

  1. ReferenceEquals (objeto, objeto) es lo mismo que "(objeto) arg1 == arg2" (así que en el caso de tipos de valor, obtienes boxeo y lleva tiempo). Pero este método es la única forma 100% segura de verificar que su argumento sea nulo en varias situaciones, como

    • a) antes de llamar a sus miembros a través de. operador
    • b) comprobar el resultado del operador AS.
  2. == y es igual a (). ¿Por qué digo que ReferenceEquals es 100% seguro con comprobaciones nulas? Imagine que escribe extensiones genéricas en bibliotecas centrales de proyectos cruzados, y digamos que restringe el tipo de parámetro genérico a algún tipo de dominio. Este tipo puede introducir el operador "==" - ahora o más tarde (y créanme, he visto mucho, este operador puede tener una lógica muy "extraña", especialmente si se trata de objetos de dominio o persistencia). Intenta verificar si su argumento es nulo y luego llama a la operación de miembro en él. Sorpresa, PUEDE tener NullRef aquí. Porque el operador == es casi lo mismo que Equals (): muy personalizado y muy impredecible. Sin embargo, hay una diferencia que debe tenerse en cuenta: si no restringe su parámetro genérico a algún tipo personalizado (== se puede usar solo si su tipo es "clase"), el operador == es el mismo que el objeto . ReferenceEquals (..). La implementación igual siempre se usa desde el tipo final, ya que es virtual.

Entonces, mi recomendación es que, cuando escriba sus propios tipos o derive de tipos conocidos, puede usar == para verificar si hay nulos. De lo contrario, use object.ReferenceEquals (arg, null).

sotonika
fuente
1

En la clase Object .Equals implementa identidad, no igualdad. Comprueba si las referencias son iguales. El código podría ser así:

public virtual Boolean Equals(Object other) {
    if (this == other) return true;
    return false;
}

Mientras implementa .Equals en su clase, debe llamar a la clase base .Equals solo si la clase base no es Object. Sí, eso es complicado.

Aún más, como las clases derivadas pueden anular .Equals y, por lo tanto, no puede llamarlo para verificar la identidad, Microsoft agregó el método estático .ReferenceEquals.

Si usa alguna clase, entonces lógicamente .Equals comprueba la igualdad y .ReferenceEquals comprueba la identidad.

Yola
fuente
1

He ampliado la excelente respuesta de Ani para mostrar las diferencias clave cuando se trata de tipos de referencia y métodos de igualdad anulados.

.

void Main()
{

    //odd os are null; evens are not null
    object o1 = null;
    object o2 = new object();
    object o3 = null;
    object o4 = new object();
    object o5 = o1;
    object o6 = o2;

    Demo d1 = new Demo(Guid.Empty);
    Demo d2 = new Demo(Guid.NewGuid());
    Demo d3 = new Demo(Guid.Empty);

    Debug.WriteLine("comparing null with null always yields true...");
    ShowResult("ReferenceEquals(o1, o1)", () => ReferenceEquals(o1, o1)); //true
    ShowResult("ReferenceEquals(o3, o1)", () => ReferenceEquals(o3, o1)); //true
    ShowResult("ReferenceEquals(o5, o1)", () => ReferenceEquals(o5, o1)); //true 
    ShowResult("o1 == o1", () => o1 == o1); //true
    ShowResult("o3 == o1", () => o3 == o1); //true
    ShowResult("o5 == o1", () => o5 == o1); //true 

    Debug.WriteLine("...though because the object's null, we can't call methods on the object (i.e. we'd get a null reference exception).");
    ShowResult("o1.Equals(o1)", () => o1.Equals(o1)); //NullReferenceException
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o3.Equals(o1)", () => o3.Equals(o1)); //NullReferenceException
    ShowResult("o3.Equals(o2)", () => o3.Equals(o2)); //NullReferenceException
    ShowResult("o5.Equals(o1)", () => o5.Equals(o1));  //NullReferenceException
    ShowResult("o5.Equals(o2)", () => o5.Equals(o1));  //NullReferenceException

    Debug.WriteLine("Comparing a null object with a non null object always yeilds false");
    ShowResult("ReferenceEquals(o1, o2)", () => ReferenceEquals(o1, o2)); //false
    ShowResult("ReferenceEquals(o2, o1)", () => ReferenceEquals(o2, o1)); //false
    ShowResult("ReferenceEquals(o3, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o4, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("ReferenceEquals(o5, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o6, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("o1 == o2)", () => o1 == o2); //false
    ShowResult("o2 == o1)", () => o2 == o1); //false
    ShowResult("o3 == o2)", () => o3 == o2); //false
    ShowResult("o4 == o1)", () => o4 == o1); //false
    ShowResult("o5 == o2)", () => o3 == o2); //false
    ShowResult("o6 == o1)", () => o4 == o1); //false
    ShowResult("o2.Equals(o1)", () => o2.Equals(o1)); //false
    ShowResult("o4.Equals(o1)", () => o4.Equals(o1)); //false
    ShowResult("o6.Equals(o1)", () => o4.Equals(o1)); //false

    Debug.WriteLine("(though again, we can't call methods on a null object:");
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o1.Equals(o4)", () => o1.Equals(o4)); //NullReferenceException
    ShowResult("o1.Equals(o6)", () => o1.Equals(o6)); //NullReferenceException

    Debug.WriteLine("Comparing 2 references to the same object always yields true");
    ShowResult("ReferenceEquals(o2, o2)", () => ReferenceEquals(o2, o2)); //true    
    ShowResult("ReferenceEquals(o6, o2)", () => ReferenceEquals(o6, o2)); //true <-- Interesting
    ShowResult("o2 == o2", () => o2 == o2); //true  
    ShowResult("o6 == o2", () => o6 == o2); //true <-- Interesting
    ShowResult("o2.Equals(o2)", () => o2.Equals(o2)); //true 
    ShowResult("o6.Equals(o2)", () => o6.Equals(o2)); //true <-- Interesting

    Debug.WriteLine("However, comparing 2 objects may yield false even if those objects have the same values, if those objects reside in different address spaces (i.e. they're references to different objects, even if the values are similar)");
    Debug.WriteLine("NB: This is an important difference between Reference Types and Value Types.");
    ShowResult("ReferenceEquals(o4, o2)", () => ReferenceEquals(o4, o2)); //false <-- Interesting
    ShowResult("o4 == o2", () => o4 == o2); //false <-- Interesting
    ShowResult("o4.Equals(o2)", () => o4.Equals(o2)); //false <-- Interesting

    Debug.WriteLine("We can override the object's equality operator though, in which case we define what's considered equal");
    Debug.WriteLine("e.g. these objects have different ids, so we treat as not equal");
    ShowResult("ReferenceEquals(d1,d2)",()=>ReferenceEquals(d1,d2)); //false
    ShowResult("ReferenceEquals(d2,d1)",()=>ReferenceEquals(d2,d1)); //false
    ShowResult("d1 == d2",()=>d1 == d2); //false
    ShowResult("d2 == d1",()=>d2 == d1); //false
    ShowResult("d1.Equals(d2)",()=>d1.Equals(d2)); //false
    ShowResult("d2.Equals(d1)",()=>d2.Equals(d1)); //false
    Debug.WriteLine("...whilst these are different objects with the same id; so we treat as equal when using the overridden Equals method...");
    ShowResult("d1.Equals(d3)",()=>d1.Equals(d3)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    ShowResult("d3.Equals(d1)",()=>d3.Equals(d1)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    Debug.WriteLine("...but as different when using the other equality tests.");
    ShowResult("ReferenceEquals(d1,d3)",()=>ReferenceEquals(d1,d3)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("ReferenceEquals(d3,d1)",()=>ReferenceEquals(d3,d1)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d1 == d3",()=>d1 == d3); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d3 == d1",()=>d3 == d1); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)


    Debug.WriteLine("For completeness, here's an example of overriding the == operator (wihtout overriding the Equals method; though in reality if overriding == you'd probably want to override Equals too).");
    Demo2 d2a = new Demo2(Guid.Empty);
    Demo2 d2b = new Demo2(Guid.NewGuid());
    Demo2 d2c = new Demo2(Guid.Empty);
    ShowResult("d2a == d2a", () => d2a == d2a); //true
    ShowResult("d2b == d2a", () => d2b == d2a); //false
    ShowResult("d2c == d2a", () => d2c == d2a); //true <-- interesting
    ShowResult("d2a != d2a", () => d2a != d2a); //false
    ShowResult("d2b != d2a", () => d2b != d2a); //true
    ShowResult("d2c != d2a", () => d2c != d2a); //false <-- interesting
    ShowResult("ReferenceEquals(d2a,d2a)", () => ReferenceEquals(d2a, d2a)); //true
    ShowResult("ReferenceEquals(d2b,d2a)", () => ReferenceEquals(d2b, d2a)); //false
    ShowResult("ReferenceEquals(d2c,d2a)", () => ReferenceEquals(d2c, d2a)); //false <-- interesting
    ShowResult("d2a.Equals(d2a)", () => d2a.Equals(d2a)); //true
    ShowResult("d2b.Equals(d2a)", () => d2b.Equals(d2a)); //false
    ShowResult("d2c.Equals(d2a)", () => d2c.Equals(d2a)); //false <-- interesting   

}



//this code's just used to help show the output in a friendly manner
public delegate bool Statement();
void ShowResult(string statementText, Statement statement)
{
    try 
    {
        Debug.WriteLine("\t{0} => {1}",statementText, statement());
    }
    catch(Exception e)
    {
        Debug.WriteLine("\t{0} => throws {1}",statementText, e.GetType());
    }
}

class Demo
{
    Guid id;
    public Demo(Guid id) { this.id = id; }
    public override bool Equals(object obj)
    {
        return Equals(obj as Demo); //if objects are of non-comparable types, obj will be converted to null
    }
    public bool Equals(Demo obj)
    {
        if (obj == null)
        {
            return false;
        }
        else
        {
            return id.Equals(obj.id);
        }
    }
    //if two objects are Equal their hashcodes must be equal
    //however, if two objects hash codes are equal it is not necessarily true that the objects are equal
    //i.e. equal objects are a subset of equal hashcodes
    //more info here: https://stackoverflow.com/a/371348/361842
    public override int GetHashCode()
    {
        return id.GetHashCode();
    }
}

class Demo2
{
    Guid id;
    public Demo2(Guid id)
    {
        this.id = id;
    }

    public static bool operator ==(Demo2 obj1, Demo2 obj2)
    {
        if (ReferenceEquals(null, obj1)) 
        {
            return ReferenceEquals(null, obj2); //true if both are null; false if only obj1 is null
        }
        else
        {
            if(ReferenceEquals(null, obj2)) 
            {
                return false; //obj1 is not null, obj2 is; therefore false
            }
            else
            {
                return obj1.id == obj2.id; //return true if IDs are the same; else return false
            }
        }
    }

    // NB: We also HAVE to override this as below if overriding the == operator; this is enforced by the compiler.  However, oddly we could choose to override it different to the below; but typically that would be a bad idea...
    public static bool operator !=(Demo2 obj1, Demo2 obj2)
    {
        return !(obj1 == obj2);
    }
}
JohnLBevan
fuente
-3

Equals()comprueba el código hash o la equivalencia según el tipo subyacente (valor / referencia) y ReferenceEquals()está destinado a comprobar siempre el código hash. ReferenceEqualsdevuelve truesi ambos objetos apuntan a la misma ubicación de memoria.

double e = 1.5;
double d = e;
object o1 = d;
object o2 = d;

Console.WriteLine(o1.Equals(o2)); // True
Console.WriteLine(Object.Equals(o1, o2)); // True
Console.WriteLine(Object.ReferenceEquals(o1, o2)); // False

Console.WriteLine(e.Equals(d)); // True
Console.WriteLine(Object.Equals(e, d)); // True
Console.WriteLine(Object.ReferenceEquals(e, d)); // False
Atulya
fuente
3
Esto no tiene sentido. Ni Equals ni ReferenceEquals miran el HashCode. Simplemente hay un requisito de que los objetos HashCodes of Equals deben ser iguales. Y los objetos no apuntan a ninguna parte ... ReferenceEquals es verdadero si y solo si sus dos argumentos son el mismo objeto de referencia o ambos son nulos.
Jim Balter