==与.NET中的Object.Equals(对象)

时间:2008-09-22 00:15:11

标签: .net

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

这是一件好事,有什么不同之处,以及何时/为什么要使用另一张?

9 个答案:

答案 0 :(得分:34)

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

答案 1 :(得分:17)

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

object.Equals method

operator ==

Overloadable Operators

Guidelines for Overriding Equals() and Operator ==

  

这是件好事,是什么?   差异,何时/为什么要你   使用一个而不是另一个?

它怎么可能是“好”或“坏”的东西?一种方法,另一种方法 - 操作者。如果引用相等是不够的,请将它们重载,否则保持原样。对于原始类型,它们只能在盒子外工作。

答案 2 :(得分:9)

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指出,如果第一个操作数为空(因为==是静态运算符),使用==可以避免异常。这是支持使用==的论据。


删除了有关==

的有争议的评论

更新最新的Microsoft文档报价,来自2019年2月检索到的.Net 4.7.2,显示他们仍然打算使两者行为相似:

Object.Equals Method

  

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


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

答案 3 :(得分:5)

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

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,因此无法在空对象上调用方法。

因为我尝试尽可能懒惰地进行编码,所以我使用==,尤其是在处理任何一个对象(或两者)都可以为null的场景时。如果我没有,我必须先进行空检查,然后才能打电话给.Equals()

答案 4 :(得分:1)

我对两者用法的理解是这样的:使用==进行概念上的相等(在上下文中,这两个参数是否意味着相同的东西?),和.Equals用于具体的相等(这两个参数在实际上是确切的同一个对象?)。

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

答案 5 :(得分:1)

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

  1. 参考相等性object.ReferenceEquals(a,b):这两个变量指向RAM中相同的确切对象。 (如果这是C,则两个变量将具有相同的确切指针。)

  2. 可互换性,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)完全一样。

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

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

答案 6 :(得分:0)

你可能想要使用.Equals,因为有些人可能会在以后出现并为你上课超载。

答案 7 :(得分:-1)

两种最常用的类型 String和Int32 ,将operator ==()和Equals()实现为值相等(而不是引用相等)。我认为可以考虑这两个定义的例子,所以我的结论是都有相同的含义。如果是微软states otherwise,我认为它们是故意引起混淆的。

答案 8 :(得分:-1)

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

示例

    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.