==和Equals()之间的C#差异


548

我在Silverlight应用程序中有一个条件,该条件比较2个字符串,由于某种原因,当我使用==它时,它返回false,.Equals()返回true

这是代码:

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

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

为什么会这样呢?



8
字符串覆盖==,但运算符不是多态的。在此代码中,==运算符在type上调用,该运算符object进行身份比较而不是值1。
Drew Noakes 2014年

12
扩展@DrewNoakes的注释:编译器==根据操作数的编译时类型选择重载。该Content属性是object。运算符不是虚拟的,因此==调用的默认实现,以进行引用相等性比较。使用Equals,该调用转到虚拟方法object.Equals(object)string重写此方法,并对字符串内容执行序数比较。请参阅msdn.microsoft.com/zh-cn/library/fkfd9eh8(v=vs.110).aspxreferencesource.microsoft.com/#mscorlib/system/string.cs,507
phoog

6
@phoog的解释很准确。应该注意的是,当的左侧==具有编译时类型object,而右侧具有编译时类型时string,则C#编译器必须选择(在这种情况下是有问题的)重载operator ==(object, object);但它发出一个编译时警告称,可能会出现意想不到的。因此,请阅读编译时警告!要解决此问题并仍然使用==,请将左侧投射到string。如果我没记错的话,警告文字就表明了这一点。
杰普·斯蒂格·尼尔森

1
@JeppeStigNielsen +1为阅读编译器警告的建议。更好的是:打开警告为错误选项,以强制每个人都注意它们。
phoog

Answers:


429

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

Equals只是一种virtual方法,其行为方式相同,因此将使用覆盖的版本(string类型比较内容)。


56
除非操作员在班上特别实施
Dominic Cronin

23
@DominicCronin这不是事实。即使在类中实现了==,由于比较左侧的类型是object,也将被忽略。看起来运算符重载是在编译时确定的,并且在编译时才知道左侧是一个对象。
MikeKulls 2012年

4
@DominicCronin我相信您的第一个语句是正确的,因为==将解析为对象,但是您的第二个语句(运算符重载以类似的方式解析)却不是。它们是完全不同的,这就是为什么.equals将解析为字符串而==将解析为object的原因。
MikeKulls

8
为了清楚object起见,类型(请注意等宽字体)在技术上应为“类型的表达式System.Object”。它与表达式所引用的实例的运行时类型没有任何关系。我认为“将用户定义的运算符视为virtual方法一样” 的说法极具误导性。它们被视为重载方法,并且仅取决于操作数的编译时类型。实际上,在计算了一组候选用户定义的运算符之后,其余的绑定过程将恰好是方法重载解析算法
Mehrdad Afshari

4
@DominicCronin令人误解的部分是virtual方法解析取决于实例的实际运行时类型,而在运算符重载解析中则完全忽略了这一点,这的确是我回答的重点。
Mehrdad Afshari '07年

314

在将对象引用与字符串进行比较时(即使对象引用指向字符串),也将==忽略特定于字符串类的运算符的特殊行为。

通常(即不处理字符串时)Equals比较,而==比较对象引用。如果您要比较的两个对象都指向一个对象的相同确切实例,那么两个对象都将返回true,但是如果一个对象具有相同的内容并且来自不同的源(是具有相同数据的单独实例),则仅等于返回true。但是,正如注释中所指出的那样,字符串是一种特殊情况,因为它会覆盖==运算符,因此当纯粹处理字符串引用(而不是对象引用)时,即使它们是单独的实例,也仅会比较它们的值。下面的代码说明了行为上的细微差别:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));
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));

输出为:

True True True
False True True
False False True

8
发现。'=='运算符比较对象引用(浅比较),而.Equals()比较对象内容(深比较)。正如@mehrdad所说,.Equals()被重写以提供深层内容比较。
安德鲁

1
我将其保留在此处,因为我认为强调发生的事情很有价值,因为您必须密切注意以实现它。(而且我认为证明正确和错误理解的代码也值得。)我希望评级不会低于
0。– BlueMonkMN

5
当然,String实现了自定义==运算符。如果没有,则使用==不会比较内容。因此,在这里使用String是一个不好的例子,因为它无法帮助我们了解没有定义自定义运算符的一般情况。
Dominic Cronin

6
史诗代码示例为+1,这使我明白了这一点。显示静态类型(左手侧类型)为对象的一般情况,以及静态类型(/ RHS类型)为字符串的特殊情况。并在字符串实习上打得很好。
barlop 2014年

2
@badsamaritan因为进行了字符串实习
Alexander Derck '17

46

==并且.Equals都取决于实际类型和呼叫站点上实际类型中定义的行为。两者都是方法/运算符,可以在任何类型上被重写,并且可以赋予作者所需的任何行为。根据我的经验,我发现人们.Equals在对象上实现是很普遍的,但是却忽视了实现operator ==。这意味着.Equals实际上==将测量值的相等性,同时将测量它们是否是相同的引用。

当我使用定义在不断变化中的新类型或编写通用算法时,我发现最佳实践如下

  • 如果我想比较C#中的引用,则Object.ReferenceEquals直接使用(在一般情况下不需要)
  • 如果我想比较值我用 EqualityComparer<T>.Default

在某些情况下,当我觉得==模棱两可时,我将Object.Reference在代码中显式使用equals消除歧义。

埃里克·利珀特(Eric Lippert)最近在一篇博客文章中谈到了为什么在CLR中存在两种平等方法。值得一读


Jared,您直接违反了Jeff著名的“最好的代码就是根本没有代码。” 这真的有道理吗?另一方面,我可以看到这源自何处以及为什么可能需要使语义明确。对于这种情况,我非常喜欢VB处理对象平等的方法。这是简短明确的。
Konrad Rudolph

@Konrad,我真的应该说“当我不熟悉类型时,我发现以下是最佳实践”。是的,VB在这里具有更好的语义,因为它确实将值和引用相等性分开。C#将两者混合在一起,有时会引起歧义错误。
JaredPar

10
这并非完全正确。==不能被覆盖,这是一个静态方法。它只能重载,这是一个重要的区别。因此,为==运算符执行的代码在编译时链接,而Equals是虚拟的并在执行时找到。
Stefan Steinegger

20

==运算符

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

。等于

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

2
这是不正确的。该==运营商可以重载任何类型的,而不仅仅是字符串。仅针对字符串描述特殊情况的异常会误解操作员的语义。如果“如果操作数是引用类型,则如果操作数引用相同的对象,则返回true,除非存在适用的重载,否则这种重载可能会更准确,除非存在适用的重载,在这种情况下,该重载的实现决定了结果”。对于Equals复杂的情况也是如此,因为它是一个虚拟方法,因此它的行为可以被覆盖也可以被重载。
phoog

19

首先,有有差别。对于数字

> 2 == 2.0
True

> 2.Equals(2.0)
False

对于弦

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

> x.Equals(null)
NullReferenceException

在这两种情况下,==行为都比.Equals


2
我不确定我是否认为将整型强制转换为浮点型==是好事。例如,是否16777216.0f等于(int)16777217,(double)16777217.0,或者两者都相等?整数类型之间的比较很好,但是仅应使用显式转换为匹配类型的值进行IMHO浮点比较。将a与a float以外的东西进行比较float,或将a double与a以外的东西进行比较,这让double我感到很惊讶,因为这是一种主要的代码味道,如果没有诊断,就无法编译。
2013年

1
@supercat我同意-这x == y并不令人沮丧x/3 == y/3(尝试x = 5y = 5.0)。
Panic Panic

我认为将/for整数除法用于C#和Java设计中是一个缺陷。帕斯卡div甚至VB.NET的` are much better. The problems with =='更糟糕的是,虽然:x==yy==z并不意味着x==z(考虑三个数字在我以前的评论)。至于您建议的关系,即使xy都是两个float或两个doublex.equals((Object)y)也不表示1.0f/x == 1.0f / y`(如果我有德鲁特,那将保证;即使==不区分正数和零数,也Equals应保证)。
2013年

这很正常,因为Equals()的第一个参数是字符串!
Whiplash

17

据我了解,答案很简单:

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

我希望我是正确的,它回答了您的问题。


15

我要补充一点,如果将对象转换为字符串,则它将正常工作。这就是为什么编译器会警告您的原因:

可能的意外参考比较;要进行值比较,请将左手边键入“ string”


1
究竟。@DominicCronin:始终遵守编译时警告。如果有object expr = XXX; if (expr == "Energy") { ... },则由于左侧为编译时类型object,因此编译器必须使用重载operator ==(object, object)。它检查引用是否相等。由于字符串实习,这是否会产生truefalse很难预测。如果您知道左侧是或类型,请在使用之前将左侧投射到。nullstringstring==
Jeppe Stig Nielsen

换种说法。==(确定使用引用相等还是值相等)取决于编译时间类型/静态类型/左侧类型。(这是在编译时分析中解析的类型)。而不是运行时类型/动态类型/ RHS类型。BlueMonkMN的代码显示了这一点,尽管不是使用强制转换。
barlop 2014年

5

因为.Equal到目前为止还没有提到该方法的静态版本,所以我想在此添加它,以进行总结并比较这3种变化。

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比较字符串没有实际区别,只要它们都是字符串类型即可。但是,如果一个为null,一个为另一种类型(例如整数),或者一个代表具有不同引用的对象,那么,如最初的问题所示,您可能会遇到比较内容是否相等可能不会返回什么的情况。你期望的。

建议的解决方案:

由于使用时与==使用.Equals事物进行比较时并不完全相同,因此可以改用静态String.Equals方法。这样,如果双方的类型都不相同,您仍将比较内容;如果一方为null,则可以避免出现异常。

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

它的编写要多一些,但我认为使用起来更安全。

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

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

参量

a

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

b

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

退货 Boolean

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


5

正如已经很好的答案的补充:此行为不仅限于字符串或比较不同的数字类型。即使两个元素都是同一基础类型的对象类型。“ ==”将不起作用。

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

VS2017中的示例


2

我在这里有点困惑。如果Content的运行时类型为字符串类型,则==和Equals都应返回true。但是,由于情况并非如此,因此Content的运行时类型不是字符串,并且对其调用Equals会执行引用相等,这解释了Equals(“ Energy Attack”)失败的原因。但是,在第二种情况下,应在编译时确定应调用哪个重载==静态运算符,该决定似乎是==(string,string)。这向我暗示了Content提供了对字符串的隐式转换。


2
你把它放回了前面。首先,Equals(“ Energy Attack”)不会失败,==是返回false的那个。==失败,因为它使用的是== from对象,而不是字符串。
MikeKulls 2011年

默认情况下,运算符==通过确定两个引用是否指示同一对象来测试引用是否相等。因此,引用类型不必实现运算符==即可获得此功能。当类型是不可变的,即实例中包含的数据不能更改时,重载运算符==以比较值相等性而不是引用相等性是有用的,因为作为不可变对象,它们可以被视为长因为它们具有相同的价值。在非不可变类型中覆盖运算符==不是一个好主意。
Wajeed-MSFT 2011年

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

2

在答案上再加一点。

.EqualsTo() 方法使您可以比较文化和区分大小写。


0

==C#中令牌用于两个不同的相等性检查运算符。当编译器遇到该令牌时,它将检查正在比较的两种类型中的任何一种是否对要比较的特定组合类型(*)或两种类型都可以转换为的类型的组合实现了相等运算符重载。如果编译器发现这样的重载,它将使用它。否则,如果这两种类型都是引用类型,并且它们不是无关的类(可能是接口,也可能是相关的类),则编译器将==视为引用比较运算符。如果两个条件都不适用,则编译将失败。

请注意,某些其他语言为这两个相等性检查运算符使用单独的标记。例如,在VB.NET中,=令牌仅在表达式中用于可重载的equity-check运算符,并Is用作引用测试或null-test运算符。=在不覆盖相等性检查运算符的类型上使用会失败,尝试将其Is用于测试引用相等性或无效性以外的任何其他用途时也会失败。

(*)类型通常只重载相等性以便与自身进行比较,但是对于类型重载相等性运算符以与其他特定类型进行比较可能很有用;例如,int可以(并且恕我直言应该但没有)定义一个等于的相等运算符以与比较float,以便16777217不会将自身报告为等于16777216f。实际上,由于未定义此类运算符,因此C#将提升intto float,将其舍入为16777216f,直到相等检查运算符看到它为止。然后,该运算符会看到两个相等的浮点数,并且将它们报告为相等,而不知道发生的舍入。


我更喜欢F#使用的方法,而不是将int-to-float的比较返回false,而根本不允许这种比较。然后,程序员可以决定是否以及如何处理这些值具有不同类型的事实。因为有时毕竟我们确实希望将其3视为等于3.0f。如果我们要求程序员说出每种情况的意图,那么就没有默认行为会导致意想不到的结果的危险,因为没有默认行为。
phoog

@phoog:我个人的感觉是,语言应具有其“正常”的相等性测试手段来实现等价关系,并禁止所有操作数的组合,而对于组合语言则不这样做。与简单地禁止这种比较相比,通过确认浮点数精确地表示一个与int匹配的整数,我没有看到在整数和浮点数之间进行语言检查相等性的巨大优势,而只是简单地禁止这种比较,但是我认为这两种方法都比让语言执行更优越比较之前的有损转换。
超级猫

0

真的很棒的答案和例子!

我想补充一下两者之间的根本区别,

运营商如==不是多态的,而Equals

牢记这一概念,如果您得出任何示例(通过查看左手和右手引用类型,并检查/知道该类型是否实际使==运算符重载并且等于),您肯定会找到正确的答案。



-2

==

==运算符可用于比较任何类型的两个变量,并且只比较位

int a = 3;
byte b = 3;
if (a == b) { // true }

注意:int的左侧还有更多零,但我们不在乎。

整数a(00000011)==字节b(00000011)

请记住,==运算符仅关心变量中位的模式。

使用==如果两个引用(基元)引用堆上的同一对象。

无论变量是引用还是原始,规则都是相同的。

Foo a = new Foo();
Foo b = new Foo();
Foo c = a;

if (a == b) { // false }
if (a == c) { // true }
if (b == c) { // false }

a == c为真a == b为假

a和c的位模式相同,因此使用==相等。

等于():

使用equals()方法查看两个不同的对象是否相等

例如两个不同的String对象,它们都代表“简”中的字符


2
这是不正确的。请考虑以下内容:object a = 3; object b = 3; Console.WriteLine(a == b);。即使值的位模式相同,输出也会为假。操作数的类型也很重要。我们“不在乎”示例中的零个数的原因是,当我们调用equals运算符时,由于隐式转换,零个数实际上是相同的
phoog

-2

Equal和==之间的唯一区别在于对象类型比较。在其他情况下,例如引用类型和值类型,它们几乎是相同的(两者都是按位相等或两者都是引用相等)。

对象:等于:按位相等==:引用相等

字符串:(字符串的等于和==相同,但是如果将字符串之一更改为对象,则比较结果将不同)等于:按位相等==:按位相等

请参阅此处以获取更多说明。


Object.Equals不一定要看按位相等。这是一个虚拟方法,并且覆盖可以执行任何所需的操作。
phoog

是的,您是对的,您可以执行任何想要覆盖它的操作。但是我们正在谈论的主题是默认实现。Object.Equals的默认实现是按位相等。
Yu Yu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.