C # .Equals (), .ReferenceEquals () i operator ==

84

Moje rozumienie tych trzech było następujące:

  • .Equals()testy na równość danych (brak lepszego opisu). .Equals()może zwrócić True dla różnych instancji tego samego obiektu i jest to najczęściej nadpisywana metoda.

  • .ReferenceEquals() sprawdza, czy dwa obiekty są tym samym wystąpieniem i czy nie można ich przesłonić.

  • ==jest taki sam jak ReferenceEquals()domyślny, ale MOŻE być nadpisany.

Ale stacja C # stwierdza:

W klasie obiektów metody Equalsi ReferenceEqualssą semantycznie równoważne, z wyjątkiem tego, że ReferenceEqualsdziałają tylko na instancjach obiektów. ReferenceEqualsMetoda jest statyczna.

Teraz nie rozumiem. Czy ktoś może rzucić na to trochę światła?

999999
źródło
Zobacz stackoverflow.com/questions/814878/ ... i wiele innych pytań dotyczących StackOverflow na ten temat.
Ian Mercer
@High mam. To tylko część, którą wyodrębniłem ze stacji C #, która mnie dezorientuje.
999999

Odpowiedzi:

87

Wydaje się, że źródłem Twojego zamieszania jest literówka w wyciągu ze stacji C #, która powinna brzmieć: „... z tą różnicą, że Equals działa tylko w przypadku wystąpień obiektów. Metoda ReferenceEquals jest statyczna”.


Masz luźną rację co do różnic w znaczeniach semantycznych każdego z nich (chociaż „różne instancje tego samego obiektu” wydają się nieco zagmatwane, prawdopodobnie powinno brzmieć „różne instancje tego samego typu ) i które można przesłonić.

Jeśli pominiemy to na bok, zajmijmy się ostatnią częścią twojego pytania, tj. Jak działają one z prostymi System.Objectinstancjami i System.Objectodniesieniami (potrzebujemy obu, aby uniknąć niepolimorficznej natury ==). Tutaj wszystkie trzy operacje będą działać tak samo , ale z zastrzeżeniem: Equalsnie można ich wywołać null.

Equalsjest metodą instancji, która przyjmuje jeden parametr (którym może być null). Ponieważ jest to metoda instancji (musi być wywołana na rzeczywistym obiekcie), nie można jej wywołać na null-reference.

ReferenceEqualsjest metodą statyczną, która przyjmuje dwa parametry, z których jeden / oba mogą być null. Ponieważ jest statyczny (nie jest powiązany z instancją obiektu ), NullReferenceExceptionw żadnych okolicznościach nie zgłosi .

==jest operatorem, który w tym przypadku ( object) zachowuje się identycznie ReferenceEquals. To też nie rzuci NullReferenceException.

Ilustrować:

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
Ani
źródło
Czy więc cytowany powyżej fragment stacji C # jest nieprawidłowy (zwłaszcza jeśli zastąpię .Equals())?
999999
1
Fragment stwierdza „w objectklasie” . Myślę, że pominąłeś tę część? W przeciwnym razie nie mówiłbyś o jego zastąpieniu.
Domenic
1
Moja odpowiedź jest tylko o objectklasie.
Ani
@Ani: poniższe zdanie było błędne, metoda statyczna może zgłosić wyjątek NullReferenceException: Ponieważ jest statyczny (nie jest powiązany z instancją obiektu), w żadnych okolicznościach nie zgłosi wyjątku NullReferenceException.
selvaraj
2
Equalsjest również metodą statyczną, objectktóra przyjmuje dwa parametry. Wtedy może być jeden lub oba null.
weston
20

Zapoznaj się z tym artykułem w witrynie MSDN na ten temat.

Myślę, że istotne punkty to:

Aby sprawdzić równość odwołań, użyj ReferenceEquals. Aby sprawdzić równość wartości, użyj opcji Równa się lub Równa się.

Domyślnie operator == sprawdza równość odwołań, określając, czy dwa odwołania wskazują ten sam obiekt, więc typy odwołań nie muszą implementować operatora ==, aby uzyskać tę funkcjonalność. Gdy typ jest niezmienny, co oznacza, że ​​danych zawartych w wystąpieniu nie można zmienić, operator przeciążenia == w celu porównania równości wartości zamiast równości odwołań może być przydatny, ponieważ jako niezmienne obiekty można je uznać za takie same, o ile mają ta sama wartość.

Mam nadzieję że to pomoże!

Alastair Pitts
źródło
6
niestety link nie działa. +1 za skopiowanie odpowiednich informacji.
Pac0
6

Twoje rozumienie .ReferenceEquals jest poprawne.

.Equals sprawdza równość danych dla typów wartości i równość odwołań dla typów niebędących wartościami (obiekty ogólne).

.Równoważności można przesłonić dla obiektów w celu wykonania jakiejś formy sprawdzania równości danych

EDYCJA: Ponadto .ReferenceEquals nie mogą być używane w typach wartości (cóż, może, ale zawsze będzie fałszywe)

Luke Schafer
źródło
3

Chcę dodać moje pięć centów na temat porównania z „null”.

  1. ReferenceEquals (obiekt, obiekt) jest tym samym, co "(obiekt) arg1 == arg2" (więc w przypadku typów wartości otrzymujesz pudełko i zajmuje to trochę czasu). Ale ta metoda jest jedynym w 100% bezpiecznym sposobem sprawdzenia argumentu pod kątem wartości null w kilku sytuacjach, takich jak

    • a) zanim zadzwonisz do swoich członków przez. operator
    • b) sprawdzenie wyniku operatora AS.
  2. == i równa się (). Dlaczego mówię, że ReferenceEquals jest w 100% bezpieczny dzięki zerowaniu? Wyobraź sobie, że piszesz ogólne rozszerzenia w podstawowych bibliotekach międzyprojektowych i powiedzmy, że ograniczasz ogólny typ parametru do pewnego typu domeny. Ten typ może wprowadzić operator "==" - teraz lub później (i wierz mi, widziałem wiele, ten operator może mieć bardzo "dziwną" logikę, szczególnie jeśli chodzi o obiekty domeny lub trwałości). Próbujesz sprawdzić argument dla null, a następnie wywołać na nim operację elementu członkowskiego. Niespodzianka, MOŻESZ mieć tutaj NullRef. Ponieważ operator == jest prawie taki sam jak Equals () - bardzo niestandardowy i bardzo nieprzewidywalny. Jest jednak różnica, którą należy wziąć pod uwagę - jeśli nie ograniczysz swojego parametru generycznego do jakiegoś niestandardowego typu (== może być używane tylko wtedy, gdy typem jest "class"), operator == jest taki sam jak obiekt . ReferenceEquals (..). Implementacja Equals jest zawsze używana od typu końcowego, ponieważ jest wirtualna.

Więc moja rekomendacja jest taka, że ​​kiedy piszesz własne typy lub wywodzisz się z dobrze znanych typów, możesz użyć == do sprawdzenia null. W przeciwnym razie użyj object.ReferenceEquals (arg, null).

sotonika
źródło
1

W klasie Object .Equals implementuje tożsamość, a nie równość. Sprawdza, czy odniesienia są równe. Kod mógłby wyglądać następująco:

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

Implementując .Equals w swojej klasie, powinieneś wywoływać klasę bazową .Equals tylko wtedy, gdy klasą bazową nie jest Object. Tak, to skomplikowane.

Co więcej, ponieważ klasy pochodne mogą przesłonić .Equals i dlatego nie można go wywołać w celu sprawdzenia tożsamości Microsoft dodała statyczną metodę .ReferenceEquals.

Jeśli używasz jakiejś klasy, logicznie .Equals sprawdza równość, a .ReferenceEquals sprawdza tożsamość.

Yola
źródło
1

Rozszerzyłem doskonałą odpowiedź Ani, aby pokazać kluczowe różnice w przypadku typów referencyjnych i zastąpionych metod równości.

.

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
źródło
-3

Equals()sprawdza kod skrótu lub równoważność w zależności od typu bazowego (wartość / odwołanie) i ReferenceEquals()ma na celu zawsze sprawdzanie kodu skrótu. ReferenceEqualszwraca, truejeśli oba obiekty wskazują na to samo miejsce w pamięci.

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
źródło
3
To bzdury. Ani Equals, ani ReferenceEquals nie sprawdzają kodu HashCode. Istnieje po prostu wymóg, aby obiekty HashCodes of Equals były równe. A obiekty nigdzie nie wskazują ... ReferenceEquals jest prawdziwe wtedy i tylko wtedy, gdy oba jego argumenty są tym samym obiektem odniesienia lub oba mają wartość null.
Jim Balter,