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

时间:2010-10-06 05:02:36

标签: c# equality

我对这三个的理解是:

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

  • .ReferenceEquals()测试两个对象是否是同一个实例,并且无法覆盖。

  • 默认情况下==ReferenceEquals()相同,但可以覆盖此内容。

但是C# station说:

  

在对象类中,Equals和   ReferenceEquals方法是   语义相同,除了   ReferenceEquals仅适用于   对象实例。该   ReferenceEquals方法是静态的。

现在我没理解。任何人都可以对此有所了解吗?

7 个答案:

答案 0 :(得分:70)

您混淆的根源似乎是C#工作站的摘录中有一个拼写错误,其中应该是:“...除了等于仅适用于对象实例。”ReferenceEquals方法是静态的。“


你对每个语义含义的差异是非常正确的(虽然“同一个对象的不同实例”似乎有点混淆,它应该读作“同一个类型的不同实例”以及哪些可以被覆盖。

如果我们把它放在一边,让我们处理你问题的最后一点,即它们如何使用普通System.Object实例和System.Object引用(我们需要两者来避免非多态性的==)。在这里,所有三个操作都将等效,但需要注意:Equals无法调用null

Equals是一个带有一个参数的实例方法(可以null)。由于它是一个实例方法(必须在实际对象上调用),因此无法在null - 引用上调用它。

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

答案 1 :(得分:17)

查看有关该主题的this MSDN article

我认为相关的观点是:

  

要检查参考相等性,请使用ReferenceEquals。要检查值是否相等,请使用等于或等于。

     

默认情况下,operator ==通过确定两个引用是否指示相同的对象来测试引用相等性,因此引用类型不需要实现operator ==以获得此功能。当一个类型是不可变的,意味着实例中包含的数据不能被改变时,重载operator ==来比较值相等而不是引用相等可能是有用的,因为作为不可变对象,只要它们具有它们就可以被认为是相同的相同的价值。

希望这有帮助!

答案 2 :(得分:6)

您对.ReferenceEquals的理解是正确的。

.Equals检查值类型的数据相等性,以及非值类型(常规对象)的引用相等性。

。可以覆盖对象以执行某种形式的数据相等性检查

编辑:另外,.ReferenceEquals不能用于值类型(它可以,但总是假的)

答案 3 :(得分:3)

想要加上与“null”比较的5美分。

  1. ReferenceEquals (object,object)与“(object)arg1 == arg2”相同(所以在值类型的情况下,你会得到拳击并且需要时间)。但是这种方法是在几种情况下检查null参数的唯一100%安全方法,比如

    • a)在通过之前调用它的成员之前。运营商
    • b)检查AS操作员的结果。
  2. ==和Equals()。为什么我说ReferenceEquals是100%安全的无效检查?想象一下,您在核心跨项目库中编写了通用扩展,并假设您将通用参数类型限制为某种域类型。这种类型可以引入“==”运算符 - 现在或以后(并且相信我,我已经看到很多,这个运算符可以有一个非常“奇怪”的逻辑,特别是如果涉及到域或持久性对象)。您尝试检查null的参数,然后在其上调用成员操作。很惊讶,你可以在这里找到NullRef。因为==运算符几乎与Equals()相同 - 非常自定义且非常不可预测。但是有一个区别,应该考虑 - 如果你不将你的泛型参数限制为某种自定义类型(==只有当你的类型是“class”时才能使用),== operator和object一样.ReferenceEquals(..)。等于实现始终使用最终类型,因为它是虚拟的。

  3. 所以我的建议是,当你编写自己的类型或从众所周知的类型派生时,你可以使用==来检查null。否则使用object.ReferenceEquals(arg,null)。

答案 4 :(得分:1)

在Object类中.Equals实现身份,而不是相等。它检查引用是否相等。代码可能是这样的:

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

在类中实现.Equals时,只有在基类不是Object时才应调用基类.Equals。是的,这很复杂。

甚至更多,因为派生类可以覆盖.Equals,所以你不能调用它来检查身份Microsoft添加了静态.ReferenceEquals方法。

如果您使用某个类,那么逻辑 .Equals检查是否相等,.ReferenceEquals检查身份。

答案 5 :(得分:1)

我在Ani's excellent answer上进行了扩展,以显示处理引用类型和重写的相等方法时的主要区别。

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

答案 6 :(得分:-1)

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