简体   繁体   English

C#.Equals()、. ReferenceEquals()和==运算符

[英]C# .Equals(), .ReferenceEquals() and == operator

My understanding of these three was: 我对这三个方面的理解是:

  • .Equals() tests for data equality (for the lack of a better description). .Equals()测试数据是否相等(缺少更好的描述)。 .Equals() can return True for different instances of the same object, and this is the most commonly overridden method. .Equals()可以为同一对象的不同实例返回True,这是最常用的方法。

  • .ReferenceEquals() tests whether or not two objects are the same instance and cannot be overridden. .ReferenceEquals()测试两个对象是否是同一实例,并且不能被覆盖。

  • == is the same as the ReferenceEquals() by default, but this CAN be overridden. 默认情况下, ==ReferenceEquals()相同,但是可以覆盖此值。

But C# station states: 但是C#站指出:

In the object class, the Equals and ReferenceEquals methods are semantically equivalent, except that the ReferenceEquals works only on object instances. 在对象类中, EqualsReferenceEquals方法在语义上等效,不同之处在于ReferenceEquals仅适用于对象实例。 The ReferenceEquals method is static. ReferenceEquals方法是静态的。

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." 造成混淆的原因似乎是C#站的摘录中有一个错字,该错字应为:“ ...,但Equals仅适用于对象实例。ReferenceEquals方法是静态的。”


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 == ). 如果我们将其放在一边,让我们处理您的问题的最后一部分,即它们如何与普通的System.Object实例和System.Object引用一起工作(我们都需要规避==的非多态性质)。 Here, all three operations will work equivalentally , but with a caveat: Equals cannot be invoked on null . 在这里,所有这三个操作将等效地工作,但有一个警告:不能在null上调用Equals

Equals is an instance method that takes one parameter (which can be null ). Equals是一个采用一个参数( 可以null )的实例方法。 Since it is an instance method (must be invoked on an actual object), it can't be invoked on a null -reference. 由于它是一个实例方法(必须在实际对象上调用),因此不能在null引用上调用。

ReferenceEquals is a static method that takes two parameters, either / both of which can be null . ReferenceEquals是一个带有两个参数的静态方法,其中两个参数都可以为null Since it is static (not associated with an object instance ), it will not throw a NullReferenceException under any circumstances. 由于它是静态的(不与对象实例相关联),因此在任何情况下都不会引发NullReferenceException

== is an operator, that, in this case ( object ), behaves identically to ReferenceEquals . ==是一个运算符,在这种情况下( object )的行为与ReferenceEquals相同。 It will not throw a NullReferenceException either. 它也不会抛出NullReferenceException

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. 看一下有关该主题的这篇MSDN文章

I think the pertinent points are: 我认为有关的要点是:

To check for reference equality, use ReferenceEquals. 要检查引用是否相等,请使用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. 默认情况下,operator ==通过确定两个引用是否指示同一对象来测试引用是否相等,因此引用类型不需要实现operator ==即可获得此功能。 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. 您对.ReferenceEquals的理解是正确的。

.Equals checks data equality for value types, and reference equality for non-value types (general objects). .Equals检查值类型的数据相等性,非值类型(通用对象)的引用相等性。

.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) 编辑:此外,.ReferenceEquals不能用于值类型(可以,但是始终为false)

Want to add my five cents about comparing with "null". 想要加上与“ null”比较的5美分。

  1. ReferenceEquals (object, object) is the same as "(object)arg1 == arg2" (so in case of value types, you get boxing and it takes time). ReferenceEquals (对象,对象)与“(object)arg1 == arg2”相同(因此,在使用值类型的情况下,需要装箱并花费时间)。 But this method is the only 100% safe way to check your argument for null in several situations, like 但是这种方法是在几种情况下检查参数是否为null的唯一100%安全的方法,例如

    • a) before calling it's members via . a)在通过调用其成员之前。 operator 算子
    • b) checking the result of AS operator. b)检查AS运算符的结果。
  2. == and Equals() . ==和Equals() Why i'm saying that ReferenceEquals is 100% safe with null-checkings? 为什么我要说ReferenceEquals使用null检查是100%安全的? 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. 您尝试检查参数是否为null,然后对其调用成员操作。 Surprise, you CAN have NullRef here. 惊喜,您可以在此处使用NullRef。 Because == operator is almost the same as Equals() - very custom and very unpredictable. 因为==运算符几乎与Equals()相同-非常自定义且非常不可预测。 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(..). 但是有一个区别,应该加以考虑-如果您不将通用参数限制为某些自定义类型(==仅在您的类型为“类”时才可以使用),==运算符与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. 因此,我的建议是,当您编写自己的类型或从知名类型派生时,可以使用==检查null。 Otherwise use object.ReferenceEquals(arg, null). 否则,请使用object.ReferenceEquals(arg,null)。

In Object class .Equals implements identity, not equality. 在Object类中,.quals实现标识,而不是相等。 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. 在类中实现.Equals时,仅当基类不是Object时,才应调用基类.Equals。 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. 甚至更多,因为派生类可以覆盖.Equals,因此您不能调用它来检查身份Microsoft添加了静态.ReferenceEquals方法。

If you use some class then for you logically .Equals checks for equality and .ReferenceEquals checks for identity. 如果您使用某个类,那么从逻辑上讲,.Equals检查是否相等,.ReferenceEquals检查身份。

I've expanded on Ani's excellent answer to show the key differences when dealing with reference types and overridden equality methods. 我已经扩展了Ani的出色答案,以显示在处理引用类型和重写的相等方法时的主要区别。

.

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. Equals()根据基础类型(值/引用Equals()检查哈希码或等效项, ReferenceEquals()旨在始终检查哈希码。 ReferenceEquals returns true if both objects point to same memory location. 如果两个对象都指向相同的内存位置,则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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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