C # . Equals() ,. ReferenceEquals()和 = = 操作符

我对这三个人的理解是:

  • .Equals()测试数据相等性(因为缺乏更好的描述)。对于同一对象的不同实例,.Equals()可以返回 True,这是最常见的重写方法。

  • .ReferenceEquals()测试两个对象是否是相同的实例并且不能被重写。

  • 缺省情况下,==ReferenceEquals()相同,但是可以覆盖这个值。

C # 站指出:

在对象类中,EqualsReferenceEquals方法是 语义上是等价的,除了 ReferenceEquals只能在 对象实例 ReferenceEquals方法是静态的。

现在我不明白了,有人能解释一下吗?

74791 次浏览

Your understanding of .ReferenceEquals is correct.

.Equals checks data equality for value types, and reference equality for non-value types (general objects).

.Equals can be overridden for objects to perform some form of data equality check

EDIT: Also, .ReferenceEquals can't be used on value types (well it can, but will always be false)

Have a look at this MSDN article on the subject.

I think the pertinent points are:

To check for reference equality, use ReferenceEquals. To check for value equality, use Equals or Equals.

By default, the operator == tests for reference equality by determining if two references indicate the same object, so reference types do not need to implement operator == in order to gain this functionality. When a type is immutable, meaning the data contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value.

Hope this helps!

The source of your confusion appears to be that there is a typo in the extract from C# station, which should read: "... except that the Equals works only on object instances. The ReferenceEquals method is static."


You are loosely correct about the differences in the semantic meanings of each (although "different instances of the same object" seems a little confused, it should probably read "different instances of the same type) and about which can be overridden.

If we leave that aside, let's deal with the last bit of your question, i.e. how they work with plainSystem.Objectinstances and System.Objectreferences (we need both to dodge the non-polymorphic nature of ==). Here, all three operations will work equivalentally, but with a caveat:Equalscannot be invoked onnull.

Equalsis an instance method that takes one parameter (which can benull). Since it is an instance method (must be invoked on an actual object), it can't be invoked on a null-reference.

ReferenceEquals is a static method that takes two parameters, either / both of which can be null. Since it is static (not associated with an object instance), it will not throw aNullReferenceException under any circumstances.

==is an operator, that, in this case (object), behaves identically to ReferenceEquals. It will not throw aNullReferenceExceptioneither.

To illustrate:

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

In Object class .Equals implements identity, not equality. It checks if references are equal. The code could be like this:

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

While implementing .Equals in your class you should call base class .Equals only if the base class is not Object. Yeh, that is complicated.

Even more, as derived classes can override .Equals and so you can't call it to check identity Microsoft added static .ReferenceEquals method.

If you use some class then for you logically .Equals checks for equality and .ReferenceEquals checks for identity.

Want to add my five cents about comparing with "null".

  1. ReferenceEquals(object, object) is the same as "(object)arg1 == arg2" (so in case of value types, you get boxing and it takes time). But this method is the only 100% safe way to check your argument for null in several situations, like

    • a) before calling it's members via . operator
    • b) checking the result of AS operator.
  2. == and Equals(). Why i'm saying that ReferenceEquals is 100% safe with null-checkings? Imagine you write generic extensions in core cross-project libs, and lets say you restrict generic parameter type to some domain type. This type can introduce "==" operator -- now or later (and believe me, I've seen much, this operator can have a very "strange" logic, especially if it comes to domain or persistence objects). You try to check you argument for null and then call member operation on it. Surprise, you CAN have NullRef here. Because == operator is almost the same as Equals() - very custom and very unpredictable. There is a difference though, which should be taken into account - if you don't restrict your generic parameter to some custom type (== can be used only if your type is "class"), == operator is the same as object.ReferenceEquals(..). Equals implementation is always used from final type, as it's virtual.

So my recommendation is, when you write your own types or derive from well-known types, you can use == to check for null. Otherwise use object.ReferenceEquals(arg, null).

I've expanded on Ani's excellent answer to show the key differences when dealing with reference types and overridden equality methods.

.

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

Equals() checks for hash code or equivalence depending on the underlying type(Value/Reference) and ReferenceEquals() is intended to always check for hash code. ReferenceEquals returns true if both objects point to same memory location.

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