C#中==和Equals()的区别

709

我在一款Silverlight应用程序中有一个条件,需要比较两个字符串。但是使用 == 时会返回 false ,而 .Equals()返回 true

代码如下:

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content.Equals("Energy Attack"))
{
    // Execute code
}

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content == "Energy Attack")
{
    // Execute code
}

为什么会发生这种情况呢?


4
参见:https://dev59.com/oHVC5IYBdhLWcg3w7Vxq - Arrow
18
String覆盖了==运算符,但运算符不具有多态性。在这段代码中,==运算符被调用到类型为object的对象上,它进行的是身份比较而非值比较。 - Drew Noakes
25
对@DrewNoakes的评论进行进一步解释:编译器根据操作数的编译时类型选择“==”重载。属性“Content”是“object”类型。运算符不是虚拟的,因此调用默认实现的“==”,进行引用相等性比较。通过Equals,调用转到虚方法“object.Equals(object)”; “string”覆盖了这个方法,并对字符串内容执行序数比较。请参阅http://msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx和http://referencesource.microsoft.com/#mscorlib/system/string.cs,507。 - phoog
8
@phoog的解释非常准确。需要注意的是,当==的左侧具有编译时类型为object,右侧具有编译时类型为string时,C#编译器必须选择(在这种情况下)存在问题的重载运算符operator ==(object, object);但它会发出编译时警告,指出可能是无意的。因此,请_阅读_编译时警告!要修复此问题并仍然使用==,请将左侧转换为string类型。如果我没记错,警告文本建议这样做。 - Jeppe Stig Nielsen
2
@JeppeStigNielsen 给出的建议值得一赞,即阅读编译器警告信息。更好的做法是打开“将警告视为错误”的选项,以强制所有人都关注这些警告信息。 - phoog
这是由于值相等性(equal方法)和引用相等性(==运算符)造成的,因为equal方法检查值,而==则检查引用。 - SiwachGaurav
20个回答

529

当在object类型的表达式上使用==时,它将解析为System.Object.ReferenceEquals

Equals只是一个virtual方法,因此将使用重写版本(对于string类型会比较内容)。


68
除非操作符在类中有特别的实现。 - Dominic Cronin
39
这不是真的。即使在类中实现了 == 运算符,它也会被忽略,因为比较运算符左侧的类型是 object。看起来,运算符重载是在编译时确定的,而在编译时它只知道左侧是一个 object 对象。 - MikeKulls
4
我相信你的第一句话是正确的,即“==”将解析为对象,但是你的第二句话,即运算符重载以类似的方式解析,是不正确的。它们是非常不同的,这就是为什么“.Equals”将解析为字符串,而“==”将解析为对象的原因。 - MikeKulls
9
要明确的是,object类型(注意等宽字体)在技术上意味着“类型为System.Object的表达式”。它与被表达式引用的实例的运行时类型无关。我认为声明“用户定义的运算符被视为virtual方法”是极其误导人的。它们被视为重载方法,并且仅取决于操作数的编译时类型。实际上,在计算候选用户定义的运算符集之后,绑定过程的其余部分将完全是方法重载解析算法。 - Mehrdad Afshari
4
误导的部分是,“虚拟”方法的解析取决于实例的实际运行时类型,而这在操作符重载解析中完全被忽略,这正是我的答案的全部意义。 - Mehrdad Afshari
显示剩余5条评论

439

当比较一个对象引用和一个字符串时(即使对象引用指向一个字符串),== 运算符在特殊的处理字符串类时会被忽略。

通常情况下(不涉及字符串的情况下),Equals 比较,而 == 比较对象引用。 如果两个对象引用都指向同一个对象实例,那么两者都将返回true,但如果其中一个具有相同的内容并来自不同的源(是具有相同数据的单独实例),则只有Equals将返回true。然而,如评论中所述,字符串是一种特殊情况,因为它重写了 == 运算符,所以当纯粹处理字符串引用(而不是对象引用)时,只会比较值,即使它们是单独的实例。以下代码说明了行为上微妙的差异:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;  // Notice: set to object variable!

Console.WriteLine($"{object.ReferenceEquals(s1, s2)} {s1 == s2} {s1.Equals(s2)}");
Console.WriteLine($"{object.ReferenceEquals(s1, s3)} {s1 == s3} {s1.Equals(s3)}");
Console.WriteLine($"{object.ReferenceEquals(s1, s4)} {s1 == s4} {s1.Equals(s4)}");

输出结果为:

True True True     // s1, s2
False True True    // s1, s3
False False True   // s1, s4

摘要:

变量 .ReferenceEquals == .Equals
s1,s2
s1,s3
s1,s4

11
没错,“==”运算符用于比较对象引用(浅比较),而.Equals()方法用于比较对象内容(深比较)。正如@mehrdad所说,.Equals()被覆盖以提供深度内容比较。 - Andrew
11
当然,String实现了自定义的==运算符。如果没有实现,使用==将不会比较内容。因此,在这里使用String是一个不好的例子,因为它不能帮助我们理解没有定义自定义运算符的一般情况。 - Dominic Cronin
13
给那个史诗级代码示例点赞,它让我明白了这个概念。展示了静态类型(左侧类型)为对象的一般情况,和静态类型(右侧类型)为字符串的具体情况。并且很好地涉及了字符串驻留的问题。 - barlop
11
当多个字符串字面值相同时,编译器会聪明地为这些引用使用相同的地址,因为在 .NET 中字符串是不可变的。 - BlueMonkMN
5
由于字符串内部化。 - Alexander Derck
显示剩余5条评论

58
==.Equals都取决于实际类型的行为和调用现场的实际类型。这两个方法/运算符都可以在任何类型上被重载,赋予作者所需的任何行为。据我经验,人们通常在对象上实现.Equals但忽略了实现运算符==。这意味着.Equals实际上会测量值的相等性,而==会测量它们是否是同一个引用。
当我使用正在被定义的新类型或编写通用算法时,我发现最佳实践如下:
  • 如果我想要比较C#中的引用,我直接使用Object.ReferenceEquals(在通用情况下不需要)
  • 如果我想要比较值,我使用EqualityComparer<T>.Default
在某些情况下,当我觉得使用==是模糊的,我会在代码中明确使用Object.ReferenceEquals来消除不确定性。
Eric Lippert最近在CLR的2种相等方法的主题上发表了一篇博客文章。值得一读。

嗯,Jared,你直接违反了Jeff的著名口号“最好的代码是没有代码”。这真的合理吗?另一方面,我可以看出这是从哪里来的,以及为什么将语义明确化可能是可取的。对于这种情况,我非常喜欢VB处理对象相等性的方式。它既简短又明确无误。 - Konrad Rudolph
@Konrad,我真的应该说“当我对一种类型不熟悉时,我发现最好的做法是以下”。是的,VB在这里有更好的语义,因为它真正地将值和引用相等分开。C#将两者混合在一起,有时会导致歧义错误。 - JaredPar
13
这并不完全正确。"=="是一个静态方法,无法被覆盖,只能进行重载,这是一个重要的区别。因此,针对"=="运算符执行的代码在编译时链接,而Equals方法是虚拟的,在执行时才会找到。 - Stefan Steinegger
这是提到的文章的实际链接(目前):https://learn.microsoft.com/en-us/archive/blogs/ericlippert/double-your-dispatch-double-your-fun - user1234567

45

== 运算符

  1. 如果操作数是值类型并且它们的相等,则返回 true,否则返回 false。
  2. 如果操作数是除字符串以外的引用类型并且两者引用同一个实例(同一对象),则返回 true,否则返回 false。
  3. 如果操作数是字符串类型并且它们的相等,则返回 true,否则返回 false。

.Equals

  1. 如果操作数是引用类型,它执行引用相等性,即如果两个引用指向同一个实例(同一对象),则返回true,否则返回false。
  2. 如果操作数是值类型,则与==运算符不同,它首先检查它们的类型,如果它们的类型相同,则执行==运算符,否则返回false。

9
这不正确。 == 操作符可以为任何类型重载,不仅仅是字符串。仅描述字符串的特殊情况会误导操作符的语义。更准确的说法可能是“如果操作数是引用类型,则返回 true 如果操作数引用相同的对象,除非有适用的重载,在这种情况下该重载的实现决定结果”虽然这可能没有太大用处。对于 Equals 也是一样的,但它是一个虚拟方法,因此它的行为可以被覆盖和重载。 - phoog
一个变量既可以是值类型也可以是引用类型。例如object i1 = 50; object i2 = 50。所以在编译时(这通常是==的重点),它们都是对象或引用类型,因此i1 == i2将返回false;但在运行时(这通常是Equals的重点),它们的实际值都是50或值类型,因此i1.Equals(i2)将返回true。这是一个奇怪的小特例,但这就是c#的工作方式。 - LongChalk

31

据我理解,答案很简单:

  1. == 比较对象的引用。
  2. .Equals 比较对象的内容。
  3. String 数据类型始终像比较内容。

希望我的回答是正确的并且回答了您的问题。


21

首先,确实有一个区别。对于数字而言,

> 2 == 2.0
True

> 2.Equals(2.0)
False

并且对于字符串

> string x = null;
> x == null
True

> x.Equals(null)
NullReferenceException

在这两种情况下,== 的行为比 .Equals 更加有用。


3
我不确定将整数类型强制转换为浮点类型使用 == 运算符是否是一件好事情。例如,16777216.0f 是否应该等于 (int)16777217、(double)16777217.0、两者皆是或都不是呢?在整数类型之间进行比较是可以的,但我认为浮点类型之间的比较只应该使用明确转换为匹配类型的值来执行。将 float 与非 float 的某些东西进行比较,或将 double 与非 double 的某些东西进行比较,让我感到很不舒服,这应该无法编译而出现诊断信息。 - supercat
1
@supercat 我同意 - 令人不安的是 x == y 并不意味着 x/3 == y/3(尝试 x = 5y = 5.0)。 - Colonel Panic
我认为在C#和Java的设计中,使用/进行整数除法是一个缺陷。Pascal的div甚至VB.NET的\都要好得多。然而,==的问题更糟:x==yy==z并不意味着x==z(考虑我之前评论中的三个数字)。至于您建议的关系,即使xy都是floatdoublex.equals((Object)y)也不能暗示1.0f/x == 1.0f/y(如果我有选择的话,它会保证;即使==不能区分正数和零,Equals`也应该)。 - supercat
这很正常,因为Equals()的第一个参数是一个字符串! - Whiplash

15

我想要补充的是,如果你将你的对象转化为字符串,那么它将正常工作。这就是为什么编译器会给你一个警告:

可能出现错误的引用比较; 要进行值比较,请将左侧类型强制转换为“字符串”


1
没错。@DominicCronin:始终注意编译时的警告。如果你有 object expr = XXX; if (expr == "Energy") { ... },那么由于左侧是编译时类型为 object,编译器必须使用重载 operator ==(object, object)。它检查引用相等性。无论这是否会给出truefalse取决于 字符串驻留(string interning) ,因此很难预测。如果你_确信_左侧为 nullstring 类型,请在使用 == 之前将左侧强制转换为 string - Jeppe Stig Nielsen
换句话说,确定一个对象使用引用相等性还是值相等性取决于编译时类型/静态类型/左侧类型(这是在编译时分析中解析的类型),而不是运行时类型/动态类型/RHS类型。BlueMonkMN的代码展示了这一点,尽管没有进行强制转换。 - barlop

7

因为迄今为止还没有提到.Equal方法的静态版本,所以我想在这里加入概括并比较这三种变体。

MyString.Equals("Somestring"))          //Method 1
MyString == "Somestring"                //Method 2
String.Equals("Somestring", MyString);  //Method 3 (static String.Equals method) - better

其中,MyString是来自代码中其他位置的变量。

背景信息和总结:

在Java中,使用==比较字符串不应该使用。我提到这一点是为了让你知道,在需要使用两种语言时,也可以用更好的方法替换C#中的==

在C#中,使用方法1或方法2比较字符串没有实际区别,只要两者都是字符串类型。然而,如果其中一个为空,另一个是另一种类型(如整数),或者一个代表具有不同引用的对象,则可能会出现与初始问题所示不同的相等性比较结果。

建议的解决方案:

因为使用==进行比较并不完全等同于使用.Equals,所以可以使用静态String.Equals方法来代替。这样,如果两侧不是相同类型,仍会比较内容,如果其中一个为空,您将避免异常。

   bool areEqual = String.Equals("Somestring", MyString);  

我认为写起来略微繁琐,但使用更加安全。

以下是从Microsoft复制的一些信息:

public static bool Equals (string a, string b);

参数

a 字符串

要比较的第一个字符串,或null

b 字符串

要比较的第二个字符串,或null

返回 Boolean

如果a的值与b的值相同,则返回true; 否则返回false。如果ab都是null,则方法返回true


7

作为对已有好答案的补充:这种行为不仅限于字符串或比较不同类型的数字。即使两个元素都是相同基础类型的对象。"=="也无法工作。

以下屏幕截图显示了比较两个对象{int}值的结果

Example From VS2017


2

之前@BlueMonkMN提供的答案还有另一个方面需要考虑。这个方面是,回答@Drahcir标题问题的答案也取决于我们如何得到string值。举个例子:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
string s5 = "te" + "st";
object s6 = s5;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));

Console.WriteLine("\n  Case1 - A method changes the value:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));

Console.WriteLine("\n  Case2 - Having only literals allows to arrive at a literal:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s5), s1 == s5, s1.Equals(s5));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s6), s1 == s6, s1.Equals(s6));

输出结果如下:
True True True

  Case1 - A method changes the value:
False True True
False False True

  Case2 - Having only literals allows to arrive at a literal:
True True True
True True True

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