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

C#.Equals(),.ReferenceEquals()和==运算符,c#,equality,C#,Equality,我对这三个问题的理解是: .Equals()测试数据相等性(因为缺少更好的描述).Equals()可以为同一对象的不同实例返回True,这是最常用的重写方法 .ReferenceEquals()测试两个对象是否为同一实例且不能被重写 默认情况下,=与ReferenceEquals()相同,但这可以被覆盖 但指出: 在对象类中,等于,并且 ReferenceEquals方法是 语义上等价,除了 ReferenceEquals仅在 对象实例。这个 ReferenceEquals方法是静态的 现在

我对这三个问题的理解是:

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

  • .ReferenceEquals()
    测试两个对象是否为同一实例且不能被重写

  • 默认情况下,
    =
    ReferenceEquals()
    相同,但这可以被覆盖

但指出:

在对象类中,
等于
,并且
ReferenceEquals
方法是 语义上等价,除了
ReferenceEquals
仅在 对象实例。这个
ReferenceEquals
方法是静态的


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

您对.ReferenceEquals的理解是正确的

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

.Equals可以被重写,以便对象执行某种形式的数据相等性检查

编辑:另外,.ReferenceEquals不能用于值类型(它可以,但始终为false)

看看这个主题

我认为相关的要点是:

要检查引用相等,请使用ReferenceEquals。要检查值是否相等,请使用Equals或Equals

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


希望这有帮助

您困惑的根源似乎是C#station的摘录中有一个打字错误,应该是:“……除了Equals仅适用于对象实例。ReferenceEquals方法是静态的。”


您大致正确地理解了每个对象的语义差异(虽然“同一对象的不同实例”似乎有点混淆,但它可能应该读为“同一类型的不同实例”),以及哪些可以被覆盖

如果我们撇开这一点不谈,让我们来讨论你问题的最后一点,即它们如何处理普通的
System.Object
实例和
System.Object
引用(我们需要两者都避开
=
的非多态性)。在这里,所有三个操作都将同等工作,但需要注意:
Equals
不能在
null
上调用

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

ReferenceEquals
是一个静态方法,它接受两个参数,其中一个/两个参数可以是
null
。由于它是静态的(与对象实例不关联),因此在任何情况下都不会抛出
NullReferenceException

=
是一个运算符,在本例中(
对象
),其行为与
引用等于
相同。它也不会抛出
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

在对象类中。Equals实现标识,而不是相等。它检查引用是否相等。代码可能如下所示:

public virtual Boolean Equals(Object other) {
    if (this == other) return true;
    return false;
}
在类中实现.Equals时,应该只在基类不是Object的情况下调用基类.Equals。是的,这很复杂

更重要的是,由于派生类可以重写.Equals,所以您不能调用它来检查标识,Microsoft添加了static.ReferenceEquals方法


如果您使用某个类,那么在逻辑上,.Equals检查相等性,.ReferenceEquals检查标识。

想添加我关于与“null”进行比较的五分钱吗

  • ReferenceEquals(object,object)与“(object)arg1==arg2)相同(因此在值类型的情况下,需要装箱,这需要时间)。但是在以下几种情况下,此方法是检查参数是否为null的唯一100%安全的方法,例如

    • a) 在通过.operator呼叫会员之前
    • b) 正在检查AS运算符的结果
  • ==和Equals()。为什么我说ReferenceEquals使用空检查是100%安全的?假设您在核心跨项目库中编写泛型扩展,并假设您将泛型参数类型限制为某个域类型。该类型可以引入“==”运算符--现在或以后(相信我,我已经看到了很多,这个操作符可能有一个非常“奇怪”的逻辑,特别是当它涉及到域或持久性对象时)。你尝试检查你的参数是否为null,然后对它调用成员操作。令人惊讶的是,你可以在这里使用NullRef。因为==操作符几乎与Equals()相同)-非常自定义且非常不可预测。但是存在一个差异,应该考虑到这一点-如果您不将泛型参数限制为某个自定义类型(=仅当您的类型为“类”时才能使用,==运算符与object相同。ReferenceEquals(…).Equals实现总是从final类型开始使用,因为它是虚拟的

  • 因此,我的建议是,当您编写自己的类型或从已知类型派生时,可以使用==检查null。否则,请使用object.ReferenceEquals(arg,null)。

    我已经展开,以显示处理引用类型和重写的相等方法时的关键区别

    • 您可以在此处看到此代码的工作版本:
    • 或者,可以将此代码粘贴到
      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);
          }
      }
      
      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