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

85

Мое понимание этих трех было:

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

  • .ReferenceEquals() проверяет, являются ли два объекта одним и тем же экземпляром и не могут быть переопределены.

  • ==то же самое, что и ReferenceEquals()по умолчанию, но это МОЖЕТ быть переопределено.

Но станция C # утверждает:

В классе объектов символы Equalsи ReferenceEquals методы семантически эквивалентны, за исключением того, что ReferenceEqualsработает только на экземпляры объектов. ReferenceEqualsМетод является статическим.

Теперь я этого не понимаю. Может кто-нибудь пролить некоторый свет на это?

999999
источник
См. Stackoverflow.com/questions/814878/… и многие другие вопросы StackOverflow по этой теме.
Ян Мерсер,
@ Хорошо. Меня смущает только та часть, которую я извлек из C # Station.
999999

Ответы:

87

Источником вашего замешательства, похоже, является опечатка в отрывке из C # station, который должен гласить: «... за исключением того, что 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: поскольку он статичен (не связан с экземпляром объекта), он не будет вызывать исключение NullReferenceException ни при каких обстоятельствах.
Selvaraj
2
Equalsтакже является статическим методом, objectпринимающим два параметра. Тогда может быть один или оба null.
Weston
20

Взгляните на эту статью MSDN по этому поводу.

Я думаю, уместные моменты:

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

По умолчанию оператор == проверяет равенство ссылок, определяя, указывают ли две ссылки на один и тот же объект, поэтому для ссылочных типов не требуется реализовывать оператор ==, чтобы получить эту функциональность. Когда тип неизменяемый, то есть данные, содержащиеся в экземпляре, не могут быть изменены, может быть полезен оператор перегрузки == для сравнения равенства значений вместо ссылочного равенства, потому что, как неизменяемые объекты, они могут считаться такими же, если у них есть такое же значение.

Надеюсь это поможет!

Аластер Питтс
источник
6
к сожалению, ссылка мертва. +1 за копирование соответствующей информации.
Pac0 04
7

Ваше понимание .ReferenceEquals верное.

.Equals проверяет равенство данных для типов значений и равенство ссылок для типов, не являющихся значениями (общие объекты).

.Equals могут быть переопределены для объектов для выполнения некоторой формы проверки равенства данных.

РЕДАКТИРОВАТЬ: Кроме того, .ReferenceEquals не может использоваться для типов значений (ну, это может, но всегда будет ложным)

Люк Шафер
источник
3

Хочу добавить свои пять центов о сравнении с "нулем".

  1. ReferenceEquals (object, object) - это то же самое, что и "(object) arg1 == arg2" (поэтому в случае типов значений вы получаете бокс, и это требует времени). Но этот метод - единственный на 100% безопасный способ проверить ваш аргумент на null в нескольких ситуациях, например

    • а) перед вызовом его членов через. оператор
    • б) проверка результата работы оператора AS.
  2. == и Equals (). Почему я говорю, что ReferenceEquals на 100% безопасен с проверкой нуля? Представьте, что вы пишете общие расширения в основных кросс-проектных библиотеках, и допустим, что вы ограничиваете общий тип параметра некоторым типом домена. Этот тип может вводить оператор «==» - сейчас или позже (и поверьте мне, я много видел, этот оператор может иметь очень «странную» логику, особенно если это касается объектов домена или персистентности). Вы пытаетесь проверить свой аргумент на нуль, а затем вызвать для него операцию члена. Сюрприз, здесь МОЖЕТ быть NullRef. Потому что оператор == почти такой же, как Equals () - очень нестандартный и очень непредсказуемый. Однако есть разница, которую следует учитывать - если вы не ограничиваете общий параметр каким-либо настраиваемым типом (== можно использовать, только если ваш тип - "класс"), оператор == совпадает с object . ReferenceEquals (..). Реализация Equals всегда используется из final типа, так как она виртуальная.

Поэтому я рекомендую, когда вы пишете свои собственные типы или производите от известных типов, вы можете использовать == для проверки на null. В противном случае используйте object.ReferenceEquals (arg, null).

Сотоника
источник
1

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

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

При реализации .Equals в своем классе вы должны вызывать базовый класс .Equals, только если базовый класс не является Object. Ага, это сложно.

Более того, поскольку производные классы могут переопределять .Equals, и поэтому вы не можете вызывать его для проверки личности, Microsoft добавила статический метод .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 of Equals были равны. И объекты никуда не указывают ... ReferenceEquals истинно тогда и только тогда, когда оба его аргумента являются одним и тем же ссылочным объектом или оба равны нулю.
Джим Балтер,