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();

intshort 都是原始类型,但与 == 比较返回 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。 (请注意,它也适用于可空类型;非空可空类型始终装箱到底层类型的实例。)

由于newAgeshort,它的Equals(object) 方法只有在您传递具有相同值的盒装short 时才会返回true。你传递的是一个装箱的int,所以它返回false。

相比之下,== 运算符被定义为采用两个ints(或shorts 或longs)。 当您使用intshort 调用它时,编译器会将short 隐式转换为int,并按值比较生成的ints。

其他使其工作的方法

原始类型也有自己的Equals() 方法,可以接受相同的类型。 如果你写age.Equals(newAge),编译器会选择int.Equals(int)作为最佳重载,并将short隐式转换为int。然后它将返回true,因为此方法只是直接比较ints。

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 == intint 会隐式转换为long 对吗? 是的,我没有实际尝试就写了这么多。 请记住,在问题的代码中,如果将int age = 25; 更改为const int age = 25;,那么结果就会改变。这是因为在这种情况下确实存在从intshort 的隐式转换。见Implicit constant expression conversions。 @SLaks 是的,但是您的答案“传递的值”的措辞可以双向解释(作为开发人员传递的值,或者在拆箱后由 CLR 实际传递的值)。我猜那些还不知道这里答案的临时用户会认为它是前者 @Rachel:但事实并非如此; default == 运算符按引用比较引用类型。对于值类型,以及重载 == 的类型,它不会。【参考方案2】:

因为接受intshort.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.Equalshttp://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# 编译器将尝试执行隐式类型转换。如果编译器可以通过添加隐式转换使所有参数满足其运算符和方法,它会毫无怨言地这样做,即使在某些情况下(尤其是相等测试!)结果可能令人惊讶。

此外,intshort 等每个值类型实际上都描述了一种值和一种对象(*)。存在隐式转换来将值转换为其他类型的值,以及将任何类型的值转换为其对应类型的对象,但不同类型的对象之间不能隐式转换。

如果使用== 运算符比较shortintshort 将隐式转换为int。如果其数值等于int 的数值,则转换为的int 将等于与之比较的int。但是,如果尝试在short 上使用Equals 方法将其与int 进行比较,那么满足Equals 方法重载的唯一隐式转换将是转换为与@ 对应的对象类型987654334@。当询问short是否匹配传入的对象时,它会观察到有问题的对象是int而不是short,从而得出不可能相等的结论。

一般来说,虽然编译器不会抱怨,但应该避免比较不同类型的东西;如果有人对将事物转换为通用形式是否会产生相同的结果感兴趣,则应该明确地执行这种转换。例如,考虑

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("0", i==f);

可以通过三种方式将intfloat 进行比较。有人可能想知道:

    int 最接近的float 值是否与float 匹配? float 的整数部分是否与int 匹配? intfloat 是否代表相同的数值。

如果尝试直接比较intfloat,编译后的代码将回答第一个问题;然而,这是否是程序员的意图远非显而易见。将比较更改为 (float)i == f 可以清楚地表明第一个含义是预期的,或者 (double)i == (double)f 会导致代码回答第三个问题(并清楚地表明这是预期的意思)。

(*) 即使 C# 规范考虑类型的值,例如System.Int32System.Int32 类型的对象,这种观点与代码在平台上运行的要求相矛盾,该平台的规范将值和对象视为居住在不同的宇宙中。此外,如果T 是引用类型,而xT,那么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:如果if 在比较之前分别转换为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.

【讨论】:

那么用 == 调用比较两个ints 的方法是什么?提示:Int32 没有 operator == 方法,而是 there is one for String 这根本不能回答问题。 @SLaks:它确实没有回答有关 int 和简短比较的具体问题,您已经回答了。我仍然觉得解释== 不只是做魔术,它最终只是调用一个方法很有趣(大多数程序员可能从未实现/覆盖任何运算符)。也许我可以对您的问题添加评论,而不是添加我自己的答案。如果您觉得我所说的相关,请随时更新您的。 请注意,基本类型上的== 不是重载运算符,而是编译为ceq IL 指令的内在语言特性。【参考方案9】:

== 在原语中

Console.WriteLine(age == newAge);          // true

在原始比较中 == 运算符的行为非常明显,在 C# 中有许多 == 运算符重载可用。

字符串 == 字符串 int == int uint == uint 长 == 长 更多

所以在这种情况下,没有从intshort 的隐式转换,但是shortint 是可能的。因此 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() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

C# 中基类,虚类,抽象类,密封类,接口的区别

equals()和==到底有啥区别啊?

== 运算符和 equals() 有啥区别? (使用哈希码()???)

String类中Equals方法和Object类中的Equals方法有啥不同?

C#中基类属性值在子类中设置,如何在基类的方法中获取子类设置的值?

steam账号分国家地区吗 怎么看自己的账号是啥区