== 或 .Equals()

79

为什么要选择其中一个而不选择另一个?


7
注意:如果有人搜索并找到这个答案,Equals==都可以被重载,因此调用它们的确切结果会有所不同。例如,string使用==进行相等性测试。还要注意,它们的语义可能是复杂的。 - Guvante
可能是C#中“==”和.Equals()的区别的重复问题。 - Michael Freidgeim
1
根据微软框架设计准则:确保Object.Equals和等号操作符具有完全相同的语义和类似的性能特征。https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/equality-operators - Dave Mackersie
12个回答

65

==是身份测试,如果正在测试的两个对象实际上是同一个对象,则返回true。Equals()执行相等性测试,如果两个对象认为自己相等,则返回true。

身份测试更快,因此您可以在不需要更昂贵的相等性测试时使用它。例如,与null或空字符串进行比较。

可能会重载其中任何一个以提供不同的行为-例如,对于Equals()进行身份测试-但出于任何人阅读您的代码的缘故,请不要这样做。


如下所指出:某些类型(如StringDateTime)提供了==运算符的重载,使其具有相等语义。因此,确切的行为取决于您要比较的对象的类型。


另请参见:


1
@Brett:我不知道,但是MSDN博客说这是可能的。 - John Millikin
2
实际上,如果您覆盖运算符==,则FxCop会强制您覆盖Equals,因为它们基本上应该是相同的,与ReferenceEquals的对比不同。 - OregonGhost
2
你怎么能说永远不应该重写Equals方法呢?如果你的对象相等但是在应用程序的不同部分创建(比如从数据库中两次获取了同一行数据),那么你很可能会认为它们是相同的。最重要的是,Equals方法应该对应于对象的身份。 - Oskar
2
"==" 运算符和其他运算符一样可以被重载。至于重载 ".Equals()",如果你需要基于值的等价性度量,而不是引用,那么你应该这样做。如果不应该通过引用进行比较(即值类型),则应该重载 "=="。 - Matthew Scharley
2
因为String重载了==运算符并在使用==时调用String.Equals(a, b)。所以对于String类型,s == r和s.Equals(r)做的是完全相同的事情。查看源代码 - Julien N
显示剩余4条评论

21

@John Millikin:

如下所指出:一些值类型,如DateTime,提供了重载==运算符以实现相等的语义。因此,确切的行为将取决于您正在比较的对象的类型。

更进一步说:

DateTime是一个结构体实现。所有结构体都是System.ValueType的子类。

由于System.ValueType的子类存储在堆栈上,没有引用指向堆,因此无法进行引用检查,只能按值进行比较。

System.ValueType重写.Equals()和==方法使用反射来进行相等性检查,它使用反射来比较每个字段的值。

由于反射有点慢,如果您自己实现了一个结构体,重要的是要重写.Equals()并添加自己的值检查代码,因为这样会更快。不要只调用base.Equals();


一个 DateTime 可能存在于堆栈上。如果它在类内部,它将存在于堆上;基本上,如果通过值而不是引用传递,两个值类型永远不可能相同,对吧? - Luis Filipe

15

其他人已经基本上为您提供了答案,但我还有一个建议。偶尔会有人发誓要以他的生命(和他所爱的人的生命)来证明.Equals更高效/更好/最佳实践或其他教条主义观点。我不能说它是否高效(好吧,在某些情况下我可以),但我可以说一个大问题会出现:.Equals需要一个对象存在。(听起来很愚蠢,但它会让人感到困惑。)

你不能这样做:

StringBuilder sb = null;
if (sb.Equals(null))
{
    // whatever
}

对我和大部分人来说,似乎很明显会出现NullReferenceException。然而,支持使用.Equals的人却忽略了这个小事实。当他们看到空引用开始弹出时,有些人甚至会被“抛”开(抱歉,我忍不住)。

(在DailyWTF发帖几年前,我曾经和一个人一起工作,他坚持要求所有的相等检查都使用.Equals而不是==。即使证明了他的错误也没用。我们只是确保打破了他的所有其他规则,以便从方法或属性返回的任何引用都不是null,并且最终解决了问题)。


在这种情况下,你可以使用 if (sb?.Equals(null) ?? true),但是那肯定比 if (sb == null) 更加笨拙和不易读。 - saluce
Object.Equals(Object obj) 的官方建议明确提到,对于 objnull 值应显式返回 false。它还指出实现不应抛出异常。尽管如此,您无法保证每个人都遵循这个明智的建议!https://learn.microsoft.com/en-us/dotnet/api/system.object.equals - jmlane
还有一个静态的Equals函数object.Equals(object, object),它可以接受空对象。例如:if (object.Equals(sb, null) {..},或者简写为if (Equals(sb, null)) {..} - user11909

6

“==” 通常表示“身份”相等,即“对象 a 实际上与对象 b 在内存中完全相同”。

“equals()” 表示对象在逻辑上相等(例如从业务角度)。因此,如果要比较用户定义类的实例,则通常需要使用并定义 equals() 方法,如果您想让像 Hashtable 这样的东西正常工作。

例如,如果您有一个带有属性“姓名”和“地址”的 Person 类,并且想将此 Person 用作 Hashtable 中包含更多信息的键,那么您需要实现 equals() 和 hash(),以便创建 Person 的实例并将其用作 Hashtable 的键来获取信息。

仅使用 “==”,您的新实例将相同。


5
根据MSDN,在C#中有两种不同的相等性:引用相等性(也称为标识)和值相等性。值相等性是相等性的通常理解意义:它意味着两个对象包含相同的值。例如,值为2的两个整数具有值相等性。引用相等性意味着没有两个要比较的对象。相反,有两个对象引用,它们都指向同一个对象。

...

默认情况下,操作符 == 通过确定两个引用是否指向同一个对象来测试引用相等性。


这个解释非常出色。同时,参考文档也是一个可靠的做法。请加入一些实际的例子来扩展它。 - carloswm85

3
Equals==都可以被重载,因此调用其中一个的确切结果会有所不同。请注意,==在编译时确定,因此实际实现可能会改变,但使用哪个==在编译时是固定的,而Equals则根据左边的运行时类型可能使用不同的实现。

例如,string执行==的等式测试。

还要注意,两者的语义可能很复杂

最佳实践是像这个例子一样实现相等性。请注意,您可以根据计划如何使用类来简化或排除所有这些内容,并且struct已经获得了大部分内容。

class ClassName
{
    public bool Equals(ClassName other)
    {
        if (other == null)
        {
            return false;
        }
        else
        {
            //Do your equality test here.
        }
    }

    public override bool Equals(object obj)
    {
        ClassName other = obj as null; //Null and non-ClassName objects will both become null
        if (obj == null)
        {
            return false;
        }
        else
        {
            return Equals(other);
        }
    }

    public bool operator ==(ClassName left, ClassName right)
    {
        if (left == null)
        {
            return right == null;
        }
        else
        {
            return left.Equals(right);
        }
    }

    public bool operator !=(ClassName left, ClassName right)
    {
        if (left == null)
        {
            return right != null;
        }
        else
        {
            return !left.Equals(right);
        }
    }

    public override int GetHashCode()
    {
        //Return something useful here, typically all members shifted or XORed together works
    }
}

这并不完全正确。== 被重载,而 Equals 被覆盖,这是一个重要的区别。因此,对于 == 运算符执行的代码在编译时链接,而 Equals 是虚拟的,并且在执行时找到。 - Stefan Steinegger
@StefanSteinegger:除非您正在编写库,否则您很少会遇到这种区别,我已经添加了一个注释。 - Guvante
“==运算符重载,而Equals方法被覆盖” -- Equals()方法既可以重载也可以被覆盖。我的意思是,显然System.Object.Equals(object)只能被覆盖。但是,没有什么阻止一个类创建额外的Equals()方法重载,实际上这正是每当一个类实现IEquatable<T>时发生的事情。 - Peter Duniho

2

这个例子是因为DateTime类实现了IEquatable接口,该接口实现了一个“用于确定实例相等性的类型特定方法”,详见MSDN


2

另一个需要考虑的因素是:如果您从另一种语言访问对象,则==运算符可能不可调用或具有不同的含义。通常最好使用可通过名称调用的替代方法。


2
在大多数情况下,它们是相同的,因此您应该使用==以确保清晰。根据Microsoft Framework设计准则:
请确保 Object.Equals 和等式运算符具有完全相同的语义和类似的性能特征。” https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/equality-operators 但有时,有人会重写Object.Equals而没有提供等式运算符。在这种情况下,您应该使用Equals来测试值相等性,并使用Object.ReferenceEquals来测试引用相等性。

1

如果您想表达所比较的对象的内容相等,请使用equals。对于原始值或者如果您想检查被比较的对象是否是同一个对象,请使用==。对于对象,== 检查对象的地址指针是否相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接