== 和 Equals() 之间的 C# 区别
Posted
技术标签:
【中文标题】== 和 Equals() 之间的 C# 区别【英文标题】:C# difference between == and Equals() 【发布时间】:2010-10-23 07:51:26 【问题描述】:我在 Silverlight 应用程序中有一个比较 2 个字符串的条件,由于某种原因,当我使用 ==
时它返回 false 而 .Equals()
返回 true。
代码如下:
if (((ListBoxItem)lstBaseMenu.SelectedItem).Content.Equals("Energy Attack"))
// Execute code
if (((ListBoxItem)lstBaseMenu.SelectedItem).Content == "Energy Attack")
// Execute code
为什么会发生这种情况?
【问题讨论】:
另见:***.com/questions/144530/or-equals 字符串覆盖==
,但运算符不是多态的。在此代码中,==
运算符在 object
类型上调用,它进行身份比较而不是值一。
扩展@DrewNoakes 的评论:编译器根据操作数的编译时类型选择==
重载。 Content
属性是 object
。运算符不是虚拟的,所以调用==
的默认实现,给出一个引用相等比较。使用 Equals,调用转到虚拟方法 object.Equals(object)
; string
覆盖此方法并对字符串内容执行序数比较。见msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx 和referencesource.microsoft.com/#mscorlib/system/string.cs,507。
@phoog 的解释很准确。需要注意的是,当==
左侧的编译时类型为object
而右侧的编译时类型为string
时,C#编译器必须选择(有问题的,在这个案例)过载operator ==(object, object)
;但它将发出编译时警告,表明它可能是无意的。所以阅读编译时警告!要解决此问题并仍然使用==
,请将左侧转换为string
。如果我没记错的话,警告文本就是这样暗示的。
@JeppeStigNielsen +1 建议阅读编译器警告。更好的是:打开 warnings-as-errors 选项以强制每个人注意它们。
【参考方案1】:
这是由于值相等(equal 方法)和引用相等(== 运算符),因为 equal 方法检查值,而相同的 == 检查引用。
== 运算符覆盖https://referencesource.microsoft.com/ 上的字符串类中可用的代码
所以现在更容易理解了,equal 方法也有两种实现,一种来自字符串类本身,一种来自对象类。它对性能的影响以及我还运行了一些基本代码并尝试了解基准。
我在下面分享结果如果我在某处错了,请更正或建议。有 3 个案例,我对所有案例都运行了相同的代码,这就是结果。
案例1:这里我使用的是字符串。 equal 方法比较两个字符串并且两个字符串具有相同的值。 string.equals(a,b)
第一次运行:5608195 个滴答声
第二次运行:5529387 滴答声
第三次运行:5622569 滴答声
总滴答声:16760151
案例2:这里我使用的是字符串。 equal() 方法(重载一个)用于比较 2 个字符串并且两个字符串具有相同的值。 a.equals(b)
第一次运行:6738583 滴答声
第二次运行:6452927 个滴答声
第 3 次运行:7168897 滴答声
总滴答数=20360407
案例 3: 这里我使用 == 运算符来比较 2 个字符串,并且两个字符串具有相同的值。 a==b
第一次运行:6652151 个滴答声
第二次运行:7514300 滴答声
第 3 次运行:7634606 滴答声
总滴答数=21801057
class Program
private static int count;
static string a = "abcdef";
static string b = "abcdef";
static void Main(string[] args)
for (int j = 1; j <= 3; j++)
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 1; i <= 1000; i++)
checkString();
sw.Stop();
Console.WriteLine(sw.ElapsedTicks);
Console.ReadLine();
public static void checkString()
for (int i = 1; i <= 100000; i++)
if (a==b)
count++;
【讨论】:
【参考方案2】:请注意,C# 中有两种不同类型的相等
1- Value Equality
(适用于 int、DateTime 和 struct 等值类型)
2- Reference Equality
(用于对象)
有两种基本的标准协议来实现相等性检查。
1- ==
和 !=
运算符。
2- virtual
Equals
方法。
== 和 != 是静态解析的,这意味着 C# 将在编译时决定哪种类型将执行比较。
例如value-type
int x = 50;
int y = 50;
Console.WriteLine (x == y); // True
但对于reference type
object x = 50;
object y = 50;
Console.WriteLine (x == y); // False
Equals()
最初是在运行时根据操作数实际类型解析的。
例如,在以下示例中,在运行时,将决定 Equals()
将应用于 int 值,结果为 true
。
object x = 5;
object y = 5;
Console.WriteLine (x.Equals (y)); // True
但是,对于引用类型,它将使用引用相等检查。
MyObject x = new MyObject();
MyObject y = x;
Console.WriteLine (x.Equals (y)); // True
注意Equals()
对struct
使用结构比较,这意味着它在结构的每个字段上调用 Equals。
【讨论】:
【参考方案3】:将对象引用与字符串进行比较时(即使对象引用引用字符串),将忽略特定于字符串类的 ==
运算符的特殊行为。
通常(即不处理字符串时),Equals
比较值,而==
比较对象引用。
如果您要比较的两个对象指的是同一个对象的确切实例,则两者都将返回 true,但如果一个对象具有相同的内容并且来自不同的源(是具有相同数据的单独实例),则只有 Equals 会返回真。但是,正如 cmets 中所指出的,字符串是一种特殊情况,因为它覆盖了 ==
运算符,因此当纯粹处理字符串引用(而不是对象引用)时,即使它们是单独的实例,也只会比较这些值。以下代码说明了行为的细微差别:
string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
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
False True True
False False True
【讨论】:
正确。 '==' 运算符比较对象引用(浅比较),而 .Equals() 比较对象内容(深度比较)。正如@mehrdad 所说, .Equals() 被覆盖以提供深度内容比较。 String 肯定实现了自定义 == 运算符。如果没有,则使用 == 不会比较内容。所以 String 在这里是一个不好的例子,因为它不能帮助我们理解没有定义自定义运算符的一般情况。 +1 为史诗代码示例,这让我明白了这一点。显示静态类型(左侧类型)为对象的一般情况和静态类型(/RHS 类型)为字符串的具体情况。并且在字符串实习方面做得很好。 当多个字符串字面量相同时,编译器足够聪明,可以对两个引用使用相同的地址,因为 .NET 中的字符串是不可变的。 @badsamaritan 因为字符串实习【参考方案4】:== 运算符
-
如果操作数是Value Types,并且它们的值相等,则返回true,否则返回false。
如果操作数是Reference Types(字符串除外)并且都引用同一个实例(同一个对象),则返回true,否则返回false。
如果操作数是string类型并且它们的值相等,则返回true,否则返回false。
.等于
-
如果操作数是Reference Types,则执行Reference Equality,即如果两者都引用相同的实例(相同的对象),则返回true,否则返回false。
如果操作数是 Value Types,则与 == 运算符不同,它首先检查它们的 type,如果它们的类型相同,则执行 == 运算符,否则返回 false。
【讨论】:
这是不正确的。==
运算符可以为任何类型重载,而不仅仅是字符串。仅为字符串描述特殊情况的异常会歪曲运算符的语义。说“如果操作数是引用类型,如果操作数引用同一个对象,则返回 true,尽管可能不是非常有用,这会更准确,除非存在适用的重载,在这种情况下,重载的实现决定了结果”。 Equals
也是如此,但它是一个虚方法,因此它的行为可以被覆盖和重载。【参考方案5】:
据我了解,答案很简单:
==
比较对象引用。
.Equals
比较对象内容。
String
数据类型总是像内容比较一样。
我希望我是正确的,它回答了你的问题。
【讨论】:
【参考方案6】:就像对已经很好的答案的补充:这种行为不仅限于字符串或比较不同的数字类型。即使两个元素都是相同底层类型的对象类型。 "==" 不起作用。
以下屏幕截图显示了比较两个对象 int - 值的结果
【讨论】:
【参考方案7】:我要补充一点,如果您将对象转换为字符串,那么它将正常工作。这就是为什么编译器会给你一个警告说:
可能的意外参考比较;进行价值比较, 将左侧转换为“字符串”
【讨论】:
没错。 @DominicCronin:始终观察编译时警告。如果你有object expr = XXX; if (expr == "Energy") ...
,那么由于左侧是编译时类型object
,编译器必须使用重载operator ==(object, object)
。它检查引用相等性。由于string interning,这是否会给出true
或false
可能很难预测。如果您知道左侧是null
或string
类型,请在使用==
之前将左侧转换为string
。
以另一种方式表达其中的一部分。 ==(在确定它是使用引用相等还是值相等时)取决于编译时类型/静态类型/左侧类型。 (这是在编译时分析中解析的类型)。而不是运行时类型/动态类型/RHS 类型。 BlueMonkMN 的代码显示了这一点,尽管没有强制转换。【参考方案8】:
由于.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 比较字符串没有实际区别,只要两者都是字符串类型。但是,如果一个为空,一个是另一种类型(如整数),或者一个表示具有不同引用的对象,那么,正如最初的问题所示,您可能会遇到比较内容是否相等可能不会返回什么你期待。
建议的解决方案:
由于在比较事物时使用==
与使用.Equals
并不完全相同,因此您可以使用静态String.Equals 方法。这样,如果两边不是同一个类型,你仍然会比较内容,如果一个为空,你将避免异常。
bool areEqual = String.Equals("Somestring", MyString);
写起来有点多,但在我看来,使用起来更安全。
以下是从 Microsoft 复制的一些信息:
public static bool Equals (string a, string b);
参数
a
字符串
要比较的第一个字符串,或null
。
b
字符串
要比较的第二个字符串,或null
。
返回Boolean
true
如果a
的值与b
的值相同;否则,false
。如果a
和b
都是null
,则该方法返回true
。
【讨论】:
【参考方案9】:为答案再添一分。
.EqualsTo()
方法让您可以根据文化和区分大小写进行比较。
【讨论】:
【参考方案10】:==
和.Equals
都依赖于实际类型中定义的行为和调用站点的实际类型。两者都只是方法/运算符,可以在任何类型上重写并给出作者想要的任何行为。根据我的经验,我发现人们在对象上实现.Equals
却忽略了实现运算符==
是很常见的。这意味着.Equals
将实际测量值的相等性,而==
将测量它们是否是相同的参考。
当我使用定义不断变化的新类型或编写泛型算法时,我发现最佳实践如下
如果我想比较 C# 中的引用,我直接使用Object.ReferenceEquals
(在通用情况下不需要)
如果我想比较值,我使用EqualityComparer<T>.Default
在某些情况下,当我觉得 ==
的用法不明确时,我会在代码中明确使用 Object.Reference
等于来消除歧义。
Eric Lippert 最近发表了一篇关于为什么 CLR 中有两种相等方法的博客文章。值得一读
http://blogs.msdn.com/ericlippert/archive/2009/04/09/double-your-dispatch-double-your-fun.aspx【讨论】:
好吧,Jared,你直接违反了 Jeff 的名言“最好的代码就是这里根本没有代码”。这真的有道理吗?另一方面,我可以看到这源于何处以及为什么可能需要明确语义。对于这种情况,我非常喜欢 VB 处理对象相等的方式。它很短并且明确。 @Konrad,我真的应该说“当我不熟悉一种类型时,我发现最佳实践如下”。是的,VB 在这里有更好的语义,因为它真正区分了值和引用相等。 C# 将两者混合在一起,偶尔会导致歧义错误。 这并不完全正确。 == 不能被覆盖,它是一个静态方法。它只能重载,这是一个重要的区别。因此,为 == 运算符执行的代码是在编译时链接的,而 Equals 是虚拟的,在执行时找到。 这里是上述文章的实际链接(目前):docs.microsoft.com/en-us/archive/blogs/ericlippert/…【参考方案11】:当==
用于object
类型的表达式时,它将解析为System.Object.ReferenceEquals
。
Equals
只是一个virtual
方法并且行为如此,因此将使用覆盖的版本(对于string
类型比较内容)。
【讨论】:
除非类中专门实现了操作符 @DominicCronin 这不是真的。即使 == 在类中实现,它也会被忽略,因为比较左侧的类型是对象。看起来运算符重载是在编译时确定的,而在编译时它只知道左侧是一个对象。 @DominicCronin 我相信您的第一条语句是正确的,因为 == 将解析为对象,但您的第二条语句(运算符重载以类似方式解析)则不是。它们完全不同,这就是为什么 .Equals 将解析为字符串,而 == 将解析为对象。 要清楚,object
类型(注意等宽字体)在技术上意味着“System.Object
类型的表达式”。它与表达式引用的实例的运行时类型没有任何关系。我认为“用户定义的运算符被视为virtual
方法”这句话极具误导性。它们被视为重载方法,仅取决于操作数的编译时类型。实际上,在计算出候选用户定义运算符集之后,绑定过程的其余部分将完全是方法重载解析算法
@DominicCronin 误导部分是 virtual
方法解析取决于实例的实际运行时类型,而在运算符重载解析中完全忽略了这一点,即确实是我回答的重点。【参考方案12】:
非常棒的答案和例子!
我只想补充一下两者的根本区别,
==
等运算符不是多态的,而Equals
是
考虑到这个概念,如果您想出任何示例(通过查看左手和右手引用类型,并检查/了解该类型实际上是否具有 == 运算符重载和 Equals 被覆盖),您肯定会得到正确答案。
【讨论】:
【参考方案13】:@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
【讨论】:
【参考方案14】:Equal 和 == 之间的唯一区别在于对象类型比较。在其他情况下,例如引用类型和值类型,它们几乎相同(两者都是按位相等或两者都是引用相等)。
对象: 等于:按位相等 ==: 引用相等
string: (equals和==对于string是一样的,但是如果string中的一个变成object,那么比较结果就会不同) 等于:按位相等 == : 按位相等
更多解释请见here。
【讨论】:
Object.Equals 不一定看按位相等。它是一个虚方法,覆盖可以为所欲为。 是的,你是对的,你可以做任何你想做的事情来覆盖它。但我们正在谈论的主题是默认实现。 Object.Equals 的默认实现是按位相等。【参考方案15】:==
== 运算符可用于比较任何类型的两个变量,它只是比较位。
int a = 3;
byte b = 3;
if (a == b) // true
注意:int 的左侧还有更多的零,但我们在这里不关心。
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 的位模式相同,因此使用 == 时它们相等。
Equal():
使用 equals() 方法查看两个不同的对象是否相等。
比如两个不同的String对象,都代表“简”中的字符
【讨论】:
这是不正确的。考虑以下内容:object a = 3; object b = 3; Console.WriteLine(a == b);
。即使值的位模式相同,输出也是错误的。操作数的类型也很重要。我们“不关心”您的示例中不同数量的零的原因是,当我们调用等号运算符时,由于隐式转换,零的数量实际上是相同的。 【参考方案16】:
当我们创建任何对象时,对象有两个部分,一个是内容,另一个是对该内容的引用。
==
比较内容和参考;
equals()
只比较内容
http://www.codeproject.com/Articles/584128/What-is-the-difference-between-equalsequals-and-Eq
【讨论】:
这不是真的。如果a
和b
都是字符串引用,那么a == b
的结果不取决于引用是否指向同一个对象。【参考方案17】:
C# 中的==
标记用于两个不同的相等检查运算符。当编译器遇到该标记时,它将检查被比较的任何一个类型是否已经为被比较的特定组合类型(*)或两种类型都可以转换为的类型组合实现了相等运算符重载。如果编译器发现这样的重载,它将使用它。否则,如果这两种类型都是引用类型并且它们不是不相关的类(可能是接口,也可能是相关的类),编译器会将==
视为引用比较运算符。如果两个条件都不适用,编译将失败。
请注意,其他一些语言对两个相等检查运算符使用单独的标记。例如,在 VB.NET 中,=
标记仅用于可重载相等检查运算符的表达式中,Is
用作引用测试或空测试运算符。在不覆盖相等检查运算符的类型上使用 =
将失败,尝试将 Is
用于测试引用相等或无效性以外的任何目的也会失败。
(*)类型通常只重载相等性以便与自身进行比较,但是对于类型来说重载相等性运算符以与其他特定类型进行比较可能很有用;例如,int
可以(恕我直言)定义了一个相等运算符以与float
进行比较,因此 16777217 不会报告自己等于 16777216f。事实上,由于没有定义这样的运算符,C# 会将int
提升为float
,在相等检查运算符看到之前将其四舍五入为 16777216f;然后该运算符看到两个相等的浮点数并将它们报告为相等,而不知道发生的舍入。
【讨论】:
比起让 int 到浮点比较返回 false,我更喜欢 F# 使用的方法,即根本不允许这样的比较。然后程序员可以决定是否以及如何处理值具有不同类型的事实。因为有时候,毕竟,我们确实希望将3
视为等于3.0f
。如果我们要求程序员在每种情况下都说明意图,那么就不会有默认行为导致意外结果的危险,因为没有默认行为。
@phoog:我个人的感觉是,语言应该让它们的“正常”相等性测试方法实现等价关系,并禁止所有它不会的操作数组合。通过确认浮点数精确表示与 int 匹配的整数,而不是简单地禁止此类比较,我看不到语言检查整数和浮点数之间相等性的巨大优势,但我认为这两种方法都优于让语言执行比较前的有损转换。【参考方案18】:
首先,是的区别。对于数字
> 2 == 2.0
True
> 2.Equals(2.0)
False
对于字符串
> string x = null;
> x == null
True
> x.Equals(null)
NullReferenceException
在这两种情况下,==
的行为都比 .Equals
更有用
【讨论】:
我不确定我是否认为使用==
运算符将整数类型强制转换为浮点类型是一件好事。例如,16777216.0f 应该等于 (int)16777217、(double)16777217.0,两者都等于,还是都不等于?整数类型之间的比较很好,但浮点比较应该只在恕我直言时使用显式转换为匹配类型的值执行。将float
与float
以外的其他东西进行比较,或者将double
与double
以外的其他东西进行比较,我觉得这是一种主要的代码气味,如果没有诊断就不能编译。
@supercat 我同意——很遗憾x == y
并不暗示x/3 == y/3
(试试x = 5
和y = 5.0
)。
我认为使用/
进行整数除法是C# 和Java 设计中的一个缺陷。 Pascal 的 div
甚至 VB.NET 的 ` are much better. The problems with
==` 更糟糕:x==y
和 y==z
并不意味着 x==z
(考虑我之前评论中的三个数字)。至于您建议的关系,即使x
和y
都是float
或两者都是double
,x.equals((Object)y)
并不意味着1.0f/x ==
1.0f/y`(如果我有我的druthers,它会保证这一点;即使==
不区分正数和零,Equals
也应该)。
这很正常,因为Equals()的第一个参数是字符串!【参考方案19】:
我在这里有点困惑。如果 Content 的运行时类型是字符串类型,那么 == 和 Equals 都应该返回 true。但是,由于情况似乎并非如此,因此 Content 的运行时类型不是字符串,并且在其上调用 Equals 正在执行引用相等,这解释了 Equals("Energy Attack") 失败的原因。然而,在第二种情况下,关于应该调用哪个重载的 == 静态运算符的决定是在编译时做出的,这个决定似乎是 ==(string,string)。这向我表明 Content 提供了到字符串的隐式转换。
【讨论】:
你把它背到前面了。首先,Equals("Energy Attack") 不会失败,== 是返回 false 的那个。 == 失败,因为它使用的是来自对象的 ==,而不是字符串。 默认情况下,运算符 == 通过确定两个引用是否表示同一个对象来测试引用相等性。因此,引用类型不必实现 operator == 即可获得此功能。当一个类型是不可变的,即包含在实例中的数据不能改变时,重载运算符 == 来比较值相等而不是引用相等可能很有用,因为作为不可变对象,它们可以被认为与 long 相同因为它们具有相同的价值。在非不可变类型中覆盖 operator == 不是一个好主意。以上是关于== 和 Equals() 之间的 C# 区别的主要内容,如果未能解决你的问题,请参考以下文章
C# 运算符 ==、StringBuilder.Equals、Object.Equals 和 Object.ReferenceEquals 之间的区别