【问题标题】: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 建议,从规范中删除该术语,因为它似乎造成的混乱多于消除的混乱。

标签: c# compare


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

          此外,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 进行比较。有人可能想知道:

          1. int 最接近的float 值是否与float 匹配?
          2. float 的整数部分是否与int 匹配?
          3. 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

          【讨论】:

            猜你喜欢
            • 2011-11-23
            • 2011-12-02
            • 1970-01-01
            • 2022-12-04
            • 2011-10-09
            • 2010-12-11
            相关资源
            最近更新 更多