Оператор C # .Equals (), .ReferenceEquals () та ==


84

Я розумів ці три:

  • .Equals()тести на рівність даних (на відсутність кращого опису). .Equals()може повернути True для різних екземплярів одного і того ж об'єкта, і це найбільш часто перевизначений метод.

  • .ReferenceEquals() перевіряє, чи є два об'єкти однаковим екземпляром і чи не можна їх замінити.

  • ==те саме, що і ReferenceEquals()за замовчуванням, але це МОЖЕ бути замінено.

Але станція C # стверджує:

У класі об'єктів методи Equalsand ReferenceEqualsі семантично еквівалентні, за винятком того, що вони ReferenceEqualsпрацюють лише на екземплярах об'єкта. ReferenceEqualsМетод є статичним.

Тепер я не розумію. Хтось може пролити це світло?



Див stackoverflow.com/questions/814878 / ... і багато інших питань StackOverflow по цій темі.
Ian Mercer

@ Високо я маю. Мене лише бентежить частина, яку я витягнув із C # Station.
999999

Відповіді:


87

Здається, джерелом вашої плутанини є помилка в витягу зі станції C #, яка повинна читати: "... за винятком того, що Equals працює лише на екземплярах об'єктів. Метод ReferenceEquals є статичним".


Ви вільно праві стосовно відмінностей у семантичних значеннях кожного (хоча "різні екземпляри одного і того ж об'єкта" здаються дещо заплутаними, його, мабуть, слід читати "різні екземпляри одного типу ) і щодо яких можна замінити.

Якщо ми залишимо це осторонь, давайте розберемося з останньою частиною вашого запитання, тобто як вони працюють із звичайними System.Objectекземплярами та System.Objectпосиланнями (нам обом потрібно уникнути неполіморфної природи ==). Тут усі три операції працюватимуть еквівалентно , але з попередженням: на них Equalsне можна посилатися null.

Equalsє методом екземпляра, який приймає один параметр (який може бути null). Оскільки це метод екземпляра (його потрібно викликати на фактичному об'єкті), його не можна викликати на null-reference.

ReferenceEqualsє статичним методом, який приймає два параметри, будь-який / обидва з яких можуть бути null. Оскільки він статичний (не пов'язаний з екземпляром об'єкта ), він не буде викидати a NullReferenceExceptionза жодних обставин.

==є оператором, який у цьому випадку ( object) поводиться ідентично ReferenceEquals. Це теж не кине NullReferenceException.

Проілюструвати:

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

Тож чи цитований вище уривок із станції C # помилковий (особливо якщо я перевизначаю .Equals())?
999999

1
В уривку зазначено objectкласі" . Я думаю, ви пропустили цю частину? Тому що інакше ви б не говорили про перевизначення цього.
Доменік

1
Моя відповідь тільки про objectкласі.
Ані

@Ani: ваше нижченаведене речення було помилковим. Статичний метод може викинути NullReferenceException: Оскільки він статичний (не пов'язаний з екземпляром об'єкта), він не викине aNullReferenceException ні за яких обставин.
selvaraj

2
Equalsтакож є статичним методом, objectякий приймає два параметри. Тоді може бути один або обидва null.
weston

20

Погляньте на цю статтю MSDN на цю тему.

Я думаю, що доречними моментами є:

Щоб перевірити рівність посилань, використовуйте ReferenceEquals. Щоб перевірити рівність значень, використовуйте Equals або Equals.

За замовчуванням оператор == перевіряє рівність посилань, визначаючи, чи два посилання вказують на один і той же об'єкт, тому для посилання типів посилань не потрібно реалізовувати operator ==, щоб отримати цю функціональність. Коли тип незмінний, тобто дані, що містяться в екземплярі, не можуть бути змінені, оператор перевантаження == для порівняння рівності значень замість посилальної рівності може бути корисним, оскільки як незмінні об'єкти їх можна вважати однаковими, доки вони мають однакове значення.

Сподіваюся, це допомагає!


6
на жаль, посилання мертве. +1 за копіювання відповідної інформації.
Pac0

6

Ваше розуміння .ReferenceEquals правильне.

.Equals перевіряє рівність даних для типів значень, а посилальна рівність для типів, що не мають значення (загальні об'єкти).

.Рівні можуть бути замінені для об'єктів, щоб виконати певну форму перевірки рівності даних

РЕДАГУВАТИ: Крім того, .ReferenceEquals не можна використовувати для типів значень (ну це може, але завжди буде помилковим)


3

Хочу додати мої п’ять центів щодо порівняння з "нуль".

  1. ReferenceEquals (об'єкт, об'єкт) - це те саме, що "(об'єкт) arg1 == arg2" (тому у випадку типів значень ви отримуєте бокс, і це вимагає часу). Але цей метод є єдиним 100% безпечним способом перевірити аргумент на нуль у кількох ситуаціях, наприклад

    • а) перед тим, як зателефонувати своїм учасникам через. оператора
    • б) перевірка результату оператора AS.
  2. == і дорівнює (). Чому я кажу, що ReferenceEquals на 100% безпечний при нульових перевірках? Уявіть, що ви пишете загальні розширення у основних бібліотеках міжпроектних проектів, і, скажімо, ви обмежуєте загальний тип параметра певним типом домену. Цей тип може ввести оператор "==" - зараз чи пізніше (і повірте мені, я багато бачив, цей оператор може мати дуже "дивну" логіку, особливо якщо мова йде про об'єкти домену чи збереження). Ви намагаєтесь перевірити аргумент на наявність нуля, а потім викликати операцію члена над ним. Сюрприз, ви МОЖЕТЕ мати тут NullRef. Оскільки оператор == майже такий самий, як і Equals () - дуже звичний і дуже непередбачуваний. Однак є різниця, яку слід враховувати - якщо ви не обмежуєте ваш загальний параметр певним користувацьким типом (== можна використовувати, лише якщо ваш тип "клас"), оператор == такий же, як і об'єкт . ReferenceEquals (..). Реалізація Equals завжди використовується з остаточного типу, оскільки вона віртуальна.

Отже, моя рекомендація полягає в тому, що коли ви пишете власні типи або походить із загальновідомих типів, ви можете використовувати == для перевірки нуля. В іншому випадку використовуйте object.ReferenceEquals (arg, null).


1

У класі Object .Equals реалізує ідентичність, а не рівність. Він перевіряє, чи однакові посилання. Код може бути таким:

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

Під час реалізації .Equals у своєму класі ви повинні викликати базовий клас .Equals, лише якщо базовий клас не є Object. Так, це складно.

Навіть більше, оскільки похідні класи можуть замінити .Equals, тому ви не можете викликати його, щоб перевірити ідентичність, доданий статичним методом .ReferenceEquals.

Якщо ви використовуєте якийсь клас, то для вас логічно .Equals перевіряє рівність та .ReferenceEquals перевіряє ідентичність.


1

Я розгорнув чудову відповідь Ані, щоб показати ключові відмінності при роботі з посилальними типами та перевизначеними методами рівності.

  • Ви можете побачити робочу версію цього коду тут: https://dotnetfiddle.net/dFKMhB
  • Крім того, цей код можна вставити в LinqPad і запустити як Language: C# Program.

.

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

-3

Equals()перевіряє хеш-код або еквівалентність залежно від базового типу (значення / посилання) і ReferenceEquals()призначений завжди перевіряти хеш-код. ReferenceEqualsповертає, trueякщо обидва об'єкти вказують на одне і те ж місце в пам'яті.

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

3
Це нонсенс. Ні Equals, ні ReferenceEquals не розглядають HashCode. Існує просто вимога, щоб HashCodes рівних об'єктів повинні бути однаковими. І об'єкти нікуди не вказують ... ReferenceEquals є істинним тоді і тільки тоді, коли обидва його аргументи є однаковим об'єктом посилання або обидва є нульовими.
Джим Балтер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.