C# 中基元的 == 和 Equals() 有啥区别?
Posted
技术标签:
【中文标题】C# 中基元的 == 和 Equals() 有啥区别?【英文标题】:What is the difference between == and Equals() for primitives in C#?C# 中基元的 == 和 Equals() 有什么区别? 【发布时间】:2014-02-11 23:27:27 【问题描述】:考虑这段代码:
int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge); //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();
int
和 short
都是原始类型,但与 ==
比较返回 true,与 Equals
比较返回 false。
为什么?
【问题讨论】:
@OrangeDog 请思考问题,然后投票结束 这缺少明显的反向尝试:Console.WriteLine(age.Equals(newAge));
重复没有解释这种行为;这就是Equals()
的一般含义。
几天前我在 Coverity 博客上回答了这个确切的问题。 blog.coverity.com/2014/01/13/inconsistent-equality
@CodesInChaos:规范实际上两次使用了术语“原始类型”,但从未定义它;这意味着原始类型是内置值类型,但这从未明确。我已向 Mads 建议,从规范中删除该术语,因为它似乎造成的混乱多于消除的混乱。
【参考方案1】:
简答:
平等很复杂。
详细解答:
基元类型会覆盖基本的object.Equals(object)
,如果装箱的object
具有相同的类型 和值,则返回true。 (请注意,它也适用于可空类型;非空可空类型始终装箱到底层类型的实例。)
由于newAge
是short
,它的Equals(object)
方法只有在您传递具有相同值的盒装short 时才会返回true。你传递的是一个装箱的int
,所以它返回false。
相比之下,==
运算符被定义为采用两个int
s(或short
s 或long
s)。
当您使用int
和short
调用它时,编译器会将short
隐式转换为int
,并按值比较生成的int
s。
其他使其工作的方法
原始类型也有自己的Equals()
方法,可以接受相同的类型。
如果你写age.Equals(newAge)
,编译器会选择int.Equals(int)
作为最佳重载,并将short
隐式转换为int
。然后它将返回true
,因为此方法只是直接比较int
s。
short
也有一个short.Equals(short)
方法,但是int
不能隐式转换为short
,所以你没有调用它。
您可以强制它通过强制转换调用此方法:
Console.WriteLine(newAge.Equals((short)age)); // true
这将直接调用short.Equals(short)
,无需装箱。如果age
大于32767,会抛出溢出异常。
您也可以调用short.Equals(object)
重载,但显式传递一个装箱对象,以便它获得相同的类型:
Console.WriteLine(newAge.Equals((object)(short)age)); // true
与前面的替代方法一样,如果它不适合 short
,则会引发溢出。
与之前的方案不同,它将short
装箱成一个对象,浪费时间和内存。
源代码:
这是来自实际源代码的Equals()
方法:
public override bool Equals(Object obj)
if (!(obj is Int16))
return false;
return m_value == ((Int16)obj).m_value;
public bool Equals(Int16 obj)
return m_value == obj;
延伸阅读:
见Eric Lippert。
【讨论】:
@SLaks,如果我们调用long == int
,int
会隐式转换为long
对吗?
是的,我没有实际尝试就写了这么多。
请记住,在问题的代码中,如果将int age = 25;
更改为const int age = 25;
,那么结果就会改变。这是因为在这种情况下确实存在从int
到short
的隐式转换。见Implicit constant expression conversions。
@SLaks 是的,但是您的答案“传递的值”的措辞可以双向解释(作为开发人员传递的值,或者在拆箱后由 CLR 实际传递的值)。我猜那些还不知道这里答案的临时用户会认为它是前者
@Rachel:但事实并非如此; default ==
运算符按引用比较引用类型。对于值类型,以及重载 ==
的类型,它不会。【参考方案2】:
因为接受int
的short.Equals
没有重载。因此,这被称为:
public override bool Equals(object obj)
return obj is short && this == (short)obj;
obj
不是 short
.. 因此,它是错误的。
【讨论】:
【参考方案3】:当您将int
传递给short
的Equals 时,您将传递object
:
所以这个伪代码运行:
return obj is short && this == (short)obj;
【讨论】:
【参考方案4】:对于值类型,.Equals
要求两个对象是相同类型且具有相同值,而==
只是测试两个值是否相同。
Object.Equals
http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx
【讨论】:
【参考方案5】:==
用于检查相等的条件,它可以被认为是一个运算符(布尔运算符),只是为了比较2个东西,这里数据类型无关紧要,因为会进行类型转换和@ 987654322@ 也用于检查等于条件,但在这种情况下,数据类型应该相同。 N Equals 是方法而不是运算符。
以下是从您提供的示例中提取的一个小示例,这将简要说明差异。
int x=1;
short y=1;
x==y;//true
y.Equals(x);//false
在上面的例子中,X和Y的值相同,即1,当我们使用==
时,它会返回true,就像==
一样,编译器会将short类型转换为int,然后结果给出。
当我们使用Equals
时,比较完成了,但是编译器没有进行类型转换,所以返回false。
各位,如果我错了,请告诉我。
【讨论】:
【参考方案6】:Equals() 是 System.Object 类的方法 语法:Public virtual bool Equals() 建议如果我们想比较两个对象的状态,那么我们应该使用 Equals() 方法
如上所述答案== 运算符比较值是否相同。
请不要与 ReferenceEqual 混淆
引用 Equals() 语法:public static bool ReferenceEquals() 判断指定对象实例是否属于同一个实例
【讨论】:
这根本不能回答问题。 SLaks 我没有用示例解释这是上述问题的基础。【参考方案7】:在方法或运算符参数不是所需类型的许多上下文中,C# 编译器将尝试执行隐式类型转换。如果编译器可以通过添加隐式转换使所有参数满足其运算符和方法,它会毫无怨言地这样做,即使在某些情况下(尤其是相等测试!)结果可能令人惊讶。
此外,int
或short
等每个值类型实际上都描述了一种值和一种对象(*)。存在隐式转换来将值转换为其他类型的值,以及将任何类型的值转换为其对应类型的对象,但不同类型的对象之间不能隐式转换。
如果使用==
运算符比较short
和int
,short
将隐式转换为int
。如果其数值等于int
的数值,则转换为的int
将等于与之比较的int
。但是,如果尝试在short 上使用Equals
方法将其与int
进行比较,那么满足Equals
方法重载的唯一隐式转换将是转换为与@ 对应的对象类型987654334@。当询问short
是否匹配传入的对象时,它会观察到有问题的对象是int
而不是short
,从而得出不可能相等的结论。
一般来说,虽然编译器不会抱怨,但应该避免比较不同类型的东西;如果有人对将事物转换为通用形式是否会产生相同的结果感兴趣,则应该明确地执行这种转换。例如,考虑
int i = 16777217;
float f = 16777216.0f;
Console.WriteLine("0", i==f);
可以通过三种方式将int
与float
进行比较。有人可能想知道:
-
与
int
最接近的float
值是否与float
匹配?
float
的整数部分是否与int
匹配?
int
和float
是否代表相同的数值。
如果尝试直接比较int
和float
,编译后的代码将回答第一个问题;然而,这是否是程序员的意图远非显而易见。将比较更改为 (float)i == f
可以清楚地表明第一个含义是预期的,或者 (double)i == (double)f
会导致代码回答第三个问题(并清楚地表明这是预期的意思)。
(*) 即使 C# 规范考虑类型的值,例如System.Int32
是 System.Int32
类型的对象,这种观点与代码在平台上运行的要求相矛盾,该平台的规范将值和对象视为居住在不同的宇宙中。此外,如果T
是引用类型,而x
是T
,那么T
类型的引用应该能够引用x
。因此,如果Int32
类型的变量v
持有Object
,则Object
类型的引用应该能够持有对v
或其内容的引用。事实上,Object
类型的引用将能够指向保存从v
复制的数据的对象,但不能指向v
本身或其内容。这表明v
及其内容都不是真正的Object
。
【讨论】:
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to int
错误。与 Java 不同,C# 没有单独的原始类型和装箱类型。它被装箱到object
,因为这是Equals()
的唯一其他重载。
第一个和第三个问题相同;转换为 float
时,确切的值已经丢失。将 float
转换为 double
不会神奇地创建新的精度。
@SLaks:根据描述运行 C# 的虚拟机的 ECMA 规范,每个值类型定义都会创建两种不同的类型。 C# 规范可能会说 List<String>.Enumerator
类型的存储位置的内容和 List<String>.Enumerator
类型的堆对象的内容是相同的,但 ECMA/CLI 规范说它们是不同的,即使在 C# 中使用它们的行为不同。
@SLaks:如果i
和f
在比较之前分别转换为double
,它们将产生16777217.0 和16777216.0,它们比较不相等。转换 i
float
将产生 16777216.0f,比较等于 f
。
@SLaks:有关存储位置类型和装箱对象类型之间区别的简单示例,请考虑方法bool SelfSame<T>(T p) return Object.ReferenceEquals((Object)p,(Object)p);
。值类型对应的装箱对象类型可以通过身份保持向上转换满足ReferenceEquals
的参数类型;但是,存储位置类型需要 非身份保留 转换。如果将T
转换为U
会产生对原始T
以外的其他内容的引用,这表明T
并不是真正的U
。【参考方案8】:
你需要意识到的是,做==
总是会调用一个方法。问题是调用==
和Equals
是否最终调用/做同样的事情。
对于引用类型,==
将始终首先检查引用是否相同 (Object.ReferenceEquals
)。另一方面,Equals
可以被覆盖,并且可以检查某些值是否相等。
编辑:回答 svick 并添加 SLaks 评论,这里是一些 IL 代码
int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s
s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.
i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.
【讨论】:
那么用 == 调用比较两个int
s 的方法是什么?提示:Int32
没有 operator ==
方法,而是 there is one for String
。
这根本不能回答问题。
@SLaks:它确实没有回答有关 int 和简短比较的具体问题,您已经回答了。我仍然觉得解释==
不只是做魔术,它最终只是调用一个方法很有趣(大多数程序员可能从未实现/覆盖任何运算符)。也许我可以对您的问题添加评论,而不是添加我自己的答案。如果您觉得我所说的相关,请随时更新您的。
请注意,基本类型上的==
不是重载运算符,而是编译为ceq
IL 指令的内在语言特性。【参考方案9】:
== 在原语中
Console.WriteLine(age == newAge); // true
在原始比较中 == 运算符的行为非常明显,在 C# 中有许多 == 运算符重载可用。
字符串 == 字符串 int == int uint == uint 长 == 长 更多所以在这种情况下,没有从int
到short
的隐式转换,但是short
到int
是可能的。因此 newAge 被转换为 int 并发生比较,返回 true 因为两者都具有相同的值。所以相当于:
Console.WriteLine(age == (int)newAge); // true
.Equals() in Primitive
Console.WriteLine(newAge.Equals(age)); //false
这里我们需要看看 Equals() 方法是什么,我们用一个短类型变量调用 Equals。所以有三种可能:
Equals(object, object) // 来自对象的静态方法 Equals(object) // 来自对象的虚方法 Equals(short) // 实现 IEquatable.Equals(short)这里不是第一种类型,因为我们只用一个 int 类型的参数调用的参数数量不同。第三个也被消除,如上所述,将 int 隐式转换为 short 是不可能的。所以这里调用了第二种类型的Equals(object)
。 short.Equals(object)
是:
bool Equals(object z)
return z is short && (short)z == this;
所以这里的条件得到了测试 z is short
这是假的,因为 z 是一个 int 所以它返回假。
Here is detailed article from Eric Lippert
【讨论】:
以上是关于C# 中基元的 == 和 Equals() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
== 运算符和 equals() 有啥区别? (使用哈希码()???)
String类中Equals方法和Object类中的Equals方法有啥不同?