web-dev-qa-db-fra.com

Opérateur C # .Equals (), .ReferenceEquals () et ==

Ma compréhension de ces trois était:

  • .Equals() teste l'égalité des données (faute d'une meilleure description). .Equals() peut renvoyer True pour différentes instances du même objet, et c'est la méthode la plus souvent remplacée.

  • .ReferenceEquals() teste si deux objets sont ou non la même instance et ne peuvent pas être remplacés.

  • == Est le même que la ReferenceEquals() par défaut, mais cela PEUT être remplacé.

Mais station C # déclare:

Dans la classe d'objets, les méthodes Equals et ReferenceEquals sont sémantiquement équivalentes, sauf que ReferenceEquals ne fonctionne que sur les instances d'objet. La méthode ReferenceEquals est statique.

Maintenant je ne comprends pas. Quelqu'un peut-il faire la lumière là-dessus?

73
999999

La source de votre confusion semble être qu'il y a une faute de frappe dans l'extrait de la station C #, qui devrait se lire: "... sauf que Equals ne fonctionne que sur les instances d'objet. La méthode ReferenceEquals est statique . "


Vous avez vaguement raison sur les différences dans les significations sémantiques de chacun (bien que "différentes instances du même objet" semble un peu confus, il devrait probablement se lire "différentes instances du même type ) et à propos desquels peuvent être remplacés.

Si nous laissons cela de côté, examinons la dernière partie de votre question, c'est-à-dire comment ils fonctionnent avec plainSystem.Objectinstances et System.Object références (nous avons besoin à la fois d'esquiver la nature non polymorphe de ==). Ici, les trois opérations fonctionneront de manière équivalente , mais avec une mise en garde: Equalsne peut pas être invoqué sur null.

Equalsest une méthode d'instance qui prend un paramètre (que peut être null). Puisqu'il s'agit d'une méthode d'instance (doit être invoquée sur un objet réel), elle ne peut pas être invoquée sur une référence null-.

ReferenceEquals est une méthode statique qui prend deux paramètres, l'un ou l'autre/les deux pouvant être null. Comme il est statique (non associé à une instance d'objet ), il ne lancera en aucun cas un NullReferenceException.

==est un opérateur qui, dans ce cas (object), se comporte de manière identique à ReferenceEquals. Il ne lancera pas de NullReferenceExceptioneither.

Pour illustrer:

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
71
Ani

Jetez un oeil à cet article MSDN sur le sujet.

Je pense que les points pertinents sont:

Pour vérifier l'égalité de référence, utilisez ReferenceEquals. Pour vérifier l'égalité des valeurs, utilisez Equals ou Equals.

Par défaut, l'opérateur == teste l'égalité des références en déterminant si deux références indiquent le même objet, les types de référence n'ont donc pas besoin d'implémenter l'opérateur == pour obtenir cette fonctionnalité. Lorsqu'un type est immuable, ce qui signifie que les données contenues dans l'instance ne peuvent pas être modifiées, la surcharge de l'opérateur == pour comparer l'égalité de valeur au lieu de l'égalité de référence peut être utile car, en tant qu'objets immuables, ils peuvent être considérés comme identiques tant qu'ils ont le même valeur.

J'espère que cela t'aides!

18
Alastair Pitts

Votre compréhension de .ReferenceEquals est correcte.

.Equals vérifie l'égalité des données pour les types de valeur et l'égalité de référence pour les types sans valeur (objets généraux).

Les égalités peuvent être remplacées pour que les objets effectuent une certaine forme de vérification de l'égalité des données

EDIT: En outre, .ReferenceEquals ne peut pas être utilisé sur les types de valeur (bien qu'il le puisse, mais sera toujours faux)

6
Luke Schafer

Je veux ajouter mes cinq cents sur la comparaison avec "null".

  1. ReferenceEquals (objet, objet) est le même que "(objet) arg1 == arg2" (donc en cas de types de valeur, vous obtenez de la boxe et cela prend du temps). Mais cette méthode est le seul moyen sûr à 100% de vérifier votre argument pour null dans plusieurs situations, comme

    • a) avant d'appeler ses membres via. opérateur
    • b) vérification du résultat de l'opérateur AS.
  2. == et égal (). Pourquoi je dis que ReferenceEquals est sûr à 100% avec des vérifications nulles? Imaginez que vous écrivez des extensions génériques dans les bibliothèques inter-projets de base, et disons que vous limitez le type de paramètre générique à un type de domaine. Ce type peut introduire l'opérateur "==" - maintenant ou plus tard (et croyez-moi, j'ai beaucoup vu, cet opérateur peut avoir une logique très "étrange", surtout s'il s'agit d'objets de domaine ou de persistance). Vous essayez de vérifier votre argument pour null, puis appelez l'opération membre sur celui-ci. Surprise, vous POUVEZ avoir NullRef ici. Parce que l'opérateur == est presque le même que Equals () - très personnalisé et très imprévisible. Il existe cependant une différence, qui doit être prise en compte - si vous ne limitez pas votre paramètre générique à un type personnalisé (== ne peut être utilisé que si votre type est "class"), l'opérateur == est le même que l'objet .ReferenceEquals (..). L'implémentation Equals est toujours utilisée à partir du type final, car elle est virtuelle.

Donc, ma recommandation est que, lorsque vous écrivez vos propres types ou dérivez de types bien connus, vous pouvez utiliser == pour vérifier la valeur null. Sinon, utilisez object.ReferenceEquals (arg, null).

3
sotonika

Dans la classe Object .Equals implémente l'identité, pas l'égalité. Il vérifie si les références sont égales. Le code pourrait être comme ceci:

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

Lors de l'implémentation de .Equals dans votre classe, vous ne devez appeler la classe de base .Equals que si la classe de base n'est pas Object. Ouais, c'est compliqué.

Encore plus, car les classes dérivées peuvent remplacer .Equals et vous ne pouvez donc pas l'appeler pour vérifier l'identité. Microsoft a ajouté la méthode statique .ReferenceEquals.

Si vous utilisez une classe, alors pour vous logiquement .Equals vérifie l'égalité et .ReferenceEquals vérifie l'identité.

1
Yola

J'ai développé l'excellente réponse d'Ani pour montrer les principales différences lorsqu'il s'agit de types de référence et de méthodes d'égalité remplacées.

.

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