Подтвердить что ты не робот

Операторы С#.Equals(),.ReferenceEquals() и ==

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

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

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

  • == по умолчанию совпадает с ReferenceEquals(), но этот CAN может быть переопределен.

Но Станция С# сообщает:

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

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

4b9b3361

Ответ 1

Источник вашей путаницы заключается в том, что в извлечении из станции С# есть опечатка, которая должна гласить: "... за исключением того, что Equals работает только на экземплярах объекта. Метод ReferenceEquals является статическим".


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

Если мы оставим в стороне, пусть дело с последним битом вашего вопроса, то есть, как они работают с простыми System.Object экземпляров и System.Object ссылки (нам нужно как увернуться не-полиморфный характер ==). Здесь все три операции будут работать одинаково, но с оговоркой: Equals не могут быть вызваны при null.

Equals - это метод экземпляра, который принимает один параметр (который может быть null). Поскольку это метод экземпляра (должен вызываться для реального объекта), он не может быть вызван для null -reference.

ReferenceEquals - это статический метод, который принимает два параметра, каждый из которых может быть null. Поскольку он статический (не связан с экземпляром объекта), он не будет генерировать 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

Ответ 2

Посмотрите эту статью MSDN по этому вопросу.

Я думаю, что соответствующие моменты:

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

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

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

Ответ 3

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

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

. Элементы могут быть переопределены для объектов, чтобы выполнить некоторую проверку равенства данных

EDIT: Кроме того,.ReferenceEquals нельзя использовать для типов значений (ну, может, но всегда будет false)

Ответ 4

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

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

    • a) перед вызовом его членов через. оператор
    • b) проверка результата оператора AS.
  • == и Equals(). Почему я говорю, что ReferenceEquals на 100% безопасен с нулевыми проверками? Представьте, что вы пишете общие расширения в основных кросс-проектах libs и можете сказать, что вы ограничиваете общий тип параметра некоторым типом домена. Этот тип может ввести оператор "==" - теперь или позже (и поверьте, я много видел, этот оператор может иметь очень "странную" логику, особенно если речь идет о объектах домена или персистентности). Вы пытаетесь проверить свой аргумент на значение null, а затем называть его членом. Удивите, у вас здесь есть NullRef. Поскольку оператор == почти такой же, как Equals() - очень обыденный и очень непредсказуемый. Однако есть разница, которая должна быть принята во внимание - если вы не ограничиваете свой общий параметр каким-либо настраиваемым типом (== может использоваться только в том случае, если ваш тип является "классом" ), оператор == тот же, что и объект .ReferenceEquals(..). Реализация Equals всегда используется из конечного типа, поскольку она виртуальна.

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

Ответ 5

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

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

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

Более того, поскольку производные классы могут переопределять .Equals и поэтому вы не можете вызвать его для проверки подлинности Microsoft добавила статический метод .ReferenceEquals.

Если вы используете какой-то класс, то для вас логически .Equals проверяет на равенство и .ReferenceEquals проверяет личность.

Ответ 6

Я расширил отличный ответ Ани, чтобы показать основные различия при работе со ссылочными типами и переопределенными методами равенства.

  • Здесь вы можете увидеть рабочую версию этого кода: 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 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 equality operator though, in which case we define what 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 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 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);
    }
}

Ответ 7

Equals() проверяет хэш-код или эквивалентность в зависимости от базового типа (Value/Reference) и 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