【问题标题】:== vs. Object.Equals(object) in .NET== 与 .NET 中的 Object.Equals(object)
【发布时间】:2010-09-11 20:59:55
【问题描述】:

所以,当我还是现在的新手时,我曾经认为这两个东西是彼此的语法糖,即使用一个而不是另一个只是个人喜好。随着时间的推移,我发现这两者并不是一回事,即使在默认实现中也是如此(参见thisthis)。为了进一步混淆这个问题,每个都可以单独覆盖/重载以具有完全不同的含义。

这是一件好事吗,有什么区别,什么时候/为什么应该使用一个而不是另一个?

【问题讨论】:

    标签: .net


    【解决方案1】:

    要回答这个问题,我们必须描述四种对象等价:

    1. Reference Equality, object.ReferenceEquals(a, b):这两个变量指向 RAM 中的同一个对象。 (如果这是 C,两个变量将具有相同的精确指针。)

    2. Interchangeability, a == b:这两个变量指的是完全可以互换的对象。因此,当 a == b 时,Func(a,b) 和 Func(b,a) 做同样的事情。

    3. 语义平等,object.Equals(a, b):在这个确切的时刻,这两个对象的含义相同。

    4. 实体相等,a.Id == b.Id:两个对象引用同一个实体,例如数据库行,但不必具有相同的内容.

    作为程序员,在处理已知类型的对象时,您需要了解在您所处的特定代码时刻适合您的业务逻辑的等价类型。

    最简单的例子是字符串与 StringBuilder 类型。字符串覆盖 ==,StringBuilder 不会:

    var aaa1 = "aaa";
    var aaa2 = $"{'a'}{'a'}{'a'}";
    var bbb = "bbb";
    
    // False because aaa1 and aaa2 are completely different objects with different locations in RAM
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaa2): {Object.ReferenceEquals(aaa1, aaa2)}");
    
    // True because aaa1 and aaa2 are completely interchangable
    Console.WriteLine($"aaa1 == aaa2: {aaa1 == aaa2}");             // True
    Console.WriteLine($"aaa1.Equals(aaa2): {aaa1.Equals(aaa2)}");   // True
    Console.WriteLine($"aaa1 == bbb: {aaa1 == bbb}");               // False
    Console.WriteLine($"aaa1.Equals(bbb): {aaa1.Equals(bbb)}");     // False
    
    // Won't compile
    // This is why string can override ==, you can not modify a string object once it is allocated
    //aaa1[0] = 'd';
    
    // aaaUpdated and aaa1 point to the same exact object in RAM
    var aaaUpdated = aaa1;
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // True
    
    // aaaUpdated is a new string, aaa1 is unmodified
    aaaUpdated += 'c';
    Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // False
    
    var aaaBuilder1 = new StringBuilder("aaa");
    var aaaBuilder2 = new StringBuilder("aaa");
    
    // False, because both string builders are different objects
    Console.WriteLine($"Object.ReferenceEquals(aaaBuider1, aaaBuider2): {Object.ReferenceEquals(aaa1, aaa2)}");
    
    // Even though both string builders have the same contents, they are not interchangable
    // Thus, == is false
    Console.WriteLine($"aaaBuider1 == aaaBuilder2: {aaaBuilder1 == aaaBuilder2}");
    
    // But, because they both have "aaa" at this exact moment in time, Equals returns true
    Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");
    
    // Modifying the contents of the string builders changes the strings, and thus
    // Equals returns false
    aaaBuilder1.Append('e');
    aaaBuilder2.Append('f');
    Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");
    

    要了解更多细节,我们可以从实体平等开始。在实体相等的情况下,实体的属性可能会随着时间而改变,但实体的主键永远不会改变。这可以用伪代码来证明:

    // Hold the current user object in a variable
    var originalUser = database.GetUser(123);
    
    // Update the user’s name
    database.UpdateUserName(123, user.Name + "son");
    
    var updatedUser = database.GetUser(123);
    
    Console.WriteLine(originalUser.Id == updatedUser.Id); // True, both objects refer to the same entity
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different
    

    转向语义相等,示例略有变化:

    var originalUser = new User() { Name = "George" };
    var updatedUser = new User() { Name = "George" };
    
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents
    Console.WriteLine(originalUser == updatedUser); // User doesn’t define ==, False
    
    updatedUser.Name = "Paul";
    
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different
    

    互换性怎么样? (覆盖==)这更复杂。让我们在上面的例子的基础上再做一点:

    var originalUser = new User() { Name = "George" };
    var updatedUser = new User() { Name = "George" };
    Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents
    
    // Does this change updatedUser? We don’t know
    DoSomethingWith(updatedUser);
    
    // Are the following equivalent?
    // SomeMethod(originalUser, updatedUser);
    // SomeMethod(updatedUser, originalUser);
    

    在上面的示例中,DoSomethingWithUser(updatedUser) 可能会更改 updatedUser。因此我们不能再保证 originalUser 和 updatedUser 对象是“相等的”。这就是用户不覆盖 == 的原因。

    何时覆盖 == 的一个很好的例子是使用不可变对象。不可变对象是公开可见状态(属性)永不改变的对象。必须在对象的构造函数中设置整个可见状态。 (因此,所有属性都是只读的。)

    var originalImmutableUser = new ImmutableUser(name: "George");
    var secondImmutableUser = new ImmutableUser(name: "George");
    
    Console.WriteLine(Object.Equals(originalImmutableUser, secondImmutableUser); // True, the objects have the same contents
    Console.WriteLine(originalImmutableUser == secondImmutableUser); // ImmutableUser defines ==, True
    
    // Won’t compile because ImmutableUser has no setters
    secondImmutableUser.Name = "Paul";
    
    // But this does compile
    var updatedImmutableUser = secondImmutableUser.SetName("Paul"); // Returns a copy of secondImmutableUser with Name changed to Paul.
    
    Console.WriteLine(object.ReferenceEquals(updatedImmutableUser, secondImmutableUser)); // False, because updatedImmutableUser is a different object in a different location in RAM
    
    // These two calls are equivalent because the internal state of an ImmutableUser can never change
    DoSomethingWith(originalImmutableUser, secondImmutableUser);
    DoSomethingWith(secondImmutableUser, originalImmutableUser);
    

    您应该使用可变对象覆盖 == 吗? (也就是说,一个内部状态可以改变的对象?)可能不会。您需要构建一个相当复杂的事件系统来保持可互换性。

    一般来说,我使用大量使用不可变对象的代码,所以我重写了 ==,因为它比 object.Equals 更具可读性。当我使用可变对象时,我不会覆盖 == 而是依赖 object.Equals。了解他们正在使用的对象是否可变是程序员的责任,因为了解某事的状态是否可以改变应该会影响您设计代码的方式。

    == 的默认实现是 object.ReferenceEquals,因为对于可变对象,只有当变量指向 RAM 中相同的确切对象时,才能保证互换性。即使对象在给定时间点具有相同的内容,(Equals 返回 true,)也不能保证对象将继续相等;因此这些对象是不可互换的。因此,当使用不覆盖 == 的可变对象时,== 的默认实现有效,因为如果 a == b,它们是同一个对象,并且 SomeFunc(a, b) 和 SomeFunc(b, a)完全一样。

    此外,如果一个类没有定义等价,(例如,考虑一个数据库连接,以及打开文件句柄等),那么 == 和 Equals 的默认实现会退回到引用相等,因为两个变量类型数据库连接,打开文件句柄等,只有当它们是数据库连接,打开文件句柄等的确切实例时才相等。在需要知道两个不同的数据库连接引用同一个数据库或两个不同的文件句柄引用磁盘上的同一个文件的业务逻辑中,实体相等可能是有意义的。

    现在,我的肥皂盒时刻。在我看来,C# 以一种令人困惑的方式处理这个话题。 == 应该用于语义相等,而不是 Equals 方法。应该有一个不同的运算符,如 ===,用于互换性,可能还有另一个运算符,====,用于引用相等。这样,新手和/或编写 CRUD 应用程序的人只需要了解 ==,而不需要了解可互换性和引用相等性的更细微的细节。

    【讨论】:

      【解决方案2】:

      Microsoft 表示,类实现者应该使 == 的行为尽可能与 Equals 相似:

      务必确保 Object.Equals 和相等运算符具有完全相同的语义

      来自http://msdn.microsoft.com/en-us/library/vstudio/7h9bszxx(v=vs.110).aspx


      如果您想确定您正在获得 IDENTITY 比较(比较参考时),请改用 ReferenceEquals

      如果类实现者没有覆盖==,则在编译时在基类中查找静态方法。如果此搜索到达Object,则使用Object.==。对于类,这与ReferenceEquals 相同。

      如果类文档不确定给定类(可能来自 Microsoft 以外的供应商)是否将 == 实现为 EqualsReferenceEquals(或者理论上可能不同于这两者), 我有时会避开==。相反,我使用可读性较差的Equals(a, b)ReferenceEquals(a, b),具体取决于我想要的含义。

      OTOH,ps2goat 提出了一个很好的观点,即如果第一个操作数为空,使用 == 可以避免异常(因为 == 是静态运算符)。这是支持使用== 的论据。


      删除了关于==的有争议的评论


      更新 2019 年 2 月检索到的来自 .Net 4.7.2 的最新 Microsoft 文档引用表明,他们仍然希望两者表现相似:

      Object.Equals Method

      某些语言(例如 C# 和 Visual Basic)支持运算符重载。当类型重载相等运算符时,它还必须重写 Equals(Object) 方法以提供相同的功能。这通常通过根据重载的相等运算符编写 Equals(Object) 方法来完成,如下例所示。


      注意:请参阅其他答案,了解 == 是静态方法与 Equals 是实例方法的后果。我并不是说行为是相同的;我观察到 Microsoft 建议使两者尽可能相似。

      【讨论】:

      • 我添加了另一个可能会改变你想法的答案(我不介意你的推理)。在空对象上调用.Equals()(抛出异常)与使用不需要实例化任一操作数的静态运算符(无异常,按预期工作)。
      • 优秀的答案。谢谢。关于您在本页其他地方的评论,关于“为什么许多这些答案都链接到那篇文章感到困惑” - 它是 StackOverflow:它不是关于阅读完整的问题并回答它,而是关于你可以多快找到要挑剔的东西问题本身。因此,为什么批评该问题的人位于页面顶部,而您最终澄清 .NET 中的混乱局面的答案正在努力争取选票。 :-)
      • Equals 是语义相等;两个对象可能相等,但有非常细微的差异。 == 表示对象是完全可互换的。在处理可变对象时,区别非常重要,因为您可以随时更改一个对象的状态,从而更改 Equals 的结果,但不能更改 == 的结果。对于不可变对象,语义是不同的,因为 Equals 和 == 永远不会改变。这就是为什么仅建议对不可变对象覆盖 == 的原因。这种区别很关键,因为在处理可变对象时,另一个线程或方法可以改变它。
      • @AndrewRondeau - Microsoft 记录的意图是 == 运算符的行为与 Equals 相同;因此在我的回答中引用。 OTOH,Microsoft 不强制执行此操作。如果您作为班级设计师选择做出您陈述的区别,那么没有什么可以阻止您。你能描述一个你认为这是可取的具体情况吗?另外,关于“你不能改变==的结果”。是的你可以;为您的类定义运算符== 的重载。通常将== 实现为Equals;确实是我参考的 Microsoft 文档 recommends 这样做。
      • 我在stackoverflow.com/a/54892972/1711103 的回答提供了何时使用 == 与 Equals 的完整示例。
      【解决方案3】:

      我打算将此作为对已接受答案的评论发布,但我认为在确定采取哪条路线时值得考虑这一点。

      dotnetfiddle:https://dotnetfiddle.net/gESLzO

      小提琴代码:

          Object a = null;
          Object b = new Object();
      
          // Ex 1
          Console.WriteLine(a == b);
          // Ex 2
          Console.WriteLine(b == a);
      
          // Ex 3     
          Console.WriteLine(b.Equals(a));
          // Ex 4
          Console.WriteLine(a.Equals(b));
      

      前 3 个 WriteLine 示例可以工作,但第四个会引发异常。 1 和 2 使用==,这是一个静态方法,不需要实例化任何一个对象。

      示例 3 有效,因为 b 已实例化。

      示例 4 失败,因为 anull,因此无法对空对象调用方法。

      因为我尝试尽可能懒惰地编写代码,所以我使用==,尤其是在处理任何一个对象(或两者)都可以为空的情况时。如果我没有,我必须先做一个空检查,然后才能调用.Equals()

      【讨论】:

      • 请注意,字符串也会出现这种情况。当然,运算符可以被覆盖,但这个答案的本质是运算符是静态的,并且不需要任何一个操作数的非空实例。
      【解决方案4】:

      当我们比较值而不是引用时,Operator == 和 Equals() 都是相同的。两者的输出相同,见下例。

      示例

          static void Main()
          {
              string x = " hello";
              string y = " hello";
              string z = string.Copy(x);
              if (x == y)
              {
                  Console.WriteLine("== Operator");
              }
              if(x.Equals(y))
              {
                  Console.WriteLine("Equals() Function Call");
              }
              if (x == z)
              {
                  Console.WriteLine("== Operator while coping a string to another.");
              }
              if (x.Equals(y))
              {
                  Console.WriteLine("Equals() Function Call while coping a string to another.");
              }
          }
      

      输出:

        == Operator
        Equals() Function Call
        == Operator while coping a string to another.
        Equals() Function Call while coping a string to another.
      

      【讨论】:

        【解决方案5】:
        string x = "hello";
        string y = String.Copy(x);
        string z = "hello";
        

        测试x是否与y指向同一个对象:

        (object)x == (object)y  // false
        x.ReferenceEquals(y)    // false
        x.ReferenceEquals(z)    // true (because x and z are both constants they
                                //       will point to the same location in memory)
        

        测试x是否与y具有相同的字符串值:

        x == y        // true
        x == z        // true
        x.Equals(y)   // true
        y == "hello"  // true
        

        请注意,这与 Java 不同。 在 Java 中,== 运算符没有重载,因此 Java 中的一个常见错误是:

        y == "hello"  // false (y is not the same object as "hello")
        

        对于 Java 中的字符串比较,您需要始终使用 .equals()

        y.equals("hello")  // true
        

        【讨论】:

        • 对于字符串运算符 == 按内容比较两个字符串。但其他引用类型并非如此
        • 我想强调一下 Vaysage 刚才所说的:以“字符串”为例是误导(或至少不完整)。它显示了字符串是如何工作的。但是字符串是一个特例。要使这个答案完整,请将string 与:(a) 单个字符,(b) 字符数组,(c) 包含多个字符字段的struct,(d) 包含多个字符字段的class 进行对比。字符字段。甚至可能需要显示 (e) 包含 struct 字段或包含 character array 字段的 class。然后做各种赋值,当结果还是true时显示。
        【解决方案6】:

        两种最常用的类型,String 和 Int32,将 operator==() 和 Equals() 实现为值相等(而不是引用相等)。我认为可以考虑这两个定义示例,因此我的结论是两者具有相同的含义。如果是微软states otherwise,我认为他们是故意造成混乱的。

        【讨论】:

        • 在 .net 中,覆盖等式/不等式运算符的类型这样做是为了强制值相等,但 C# 添加了自己的等式/不等式运算符重载来检查不包括的对象的引用相等价值平等检验。就个人而言,我不喜欢这样的语言设计(vb.net 使用运算符 IsIsNot 来测试引用相等性;当应用于框架类型时,=<> 将测试值相等性,如果它们完全编译的话。然而,没有什么可以阻止任何类型重载这些运算符以表示完全不同的东西。
        【解决方案7】:

        您可能希望使用 .Equals,因为稍后有人可能会出现并为您的课程重载它们。

        【讨论】:

        • 是的,我想我的意思是反过来说。
        【解决方案8】:

        MSDN 对这两件事都有清晰而可靠的描述。

        object.Equals method

        operator ==

        Overloadable Operators

        Guidelines for Overriding Equals() and Operator ==

        这是一件好事吗? 差异,以及何时/为什么应该 使用一个而不是另一个?

        它怎么可能是“好”或“坏”的东西?一个 - 方法,另一个 - 运算符。如果引用相等性不够,则重载它们,否则保持原样。对于原始类型,它们只是开箱即用。

        【讨论】:

        • 当有人开始研究泛型时,当你盲目地在任何类型 T 上调用它们时,差异可能会很大。
        • “盲目”对任何事情都是不好的做法。如果您知道问题的答案,为什么还要问?
        • 即使我确实知道一个具体的答案(我不知道),也许出于同样的原因人们提出问题并自己回答?另外,你怎么能对泛型类型 T 做任何其他事情?如果你开始做 if (typeof(T) == typeof(int)) 之类的事情,那有什么意义呢?
        • @aku:如果您总结这两个运算符之间的本质区别,这个答案会更方便。直到 第四个链接(覆盖指南),微软才开始同时讨论这两个相等性——这是回答问题所必需的。 (而且我几乎没有费心点击第 4 个链接,因为它的标题听起来不太好,第 3 个链接似乎完全无关紧要。)
        • 为什么这是公认的答案?这是一个糟糕的答案。
        【解决方案9】:

        我对两者用法的理解是这样的:使用 == 表示概念上的相等(在上下文中,这两个参数是否表示相同的意思?),以及 .Equals 表示具体相等(这两个参数实际上是否准确同一个对象?)。

        编辑:Kevin Sheffield 的链接文章在解释价值与参考平等方面做得更好……

        【讨论】:

        • 不正确。 ReferenceEquals 是 .Net 中的 identity 测试。如果Equals 总是进行身份测试,那么两者都没有意义......
        • 恰恰相反。 == 表示具体相等,Equals 表示概念相等。
        猜你喜欢
        • 2012-06-02
        • 1970-01-01
        • 1970-01-01
        • 2016-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-05
        • 1970-01-01
        相关资源
        最近更新 更多