简体   繁体   中英

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

My understanding of these three was:

  • .Equals() tests for data equality (for the lack of a better description). .Equals() can return True for different instances of the same object, and this is the most commonly overridden method.

  • .ReferenceEquals() tests whether or not two objects are the same instance and cannot be overridden.

  • == is the same as the ReferenceEquals() by default, but this CAN be overridden.

But C# station states:

In the object class, the Equals and ReferenceEquals methods are semantically equivalent, except that the ReferenceEquals works only on object instances. The ReferenceEquals method is static.

Now I don't get it. Can anyone shed some light on this?

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, ie how they work with plain System.Object instances and System.Object references (we need both to dodge the non-polymorphic nature of == ). Here, all three operations will work equivalentally , but with a caveat: Equals cannot be invoked on null .

Equals is an instance method that takes one parameter (which can be null ). 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 a NullReferenceException under any circumstances.

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

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

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!

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)

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).

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM