为什么整数零不等于长零?

时间:2012-02-23 19:57:51

标签: c# .net

我刚刚在C#中发现的一段奇怪的代码(对于使用.NET structs的其他CLI语言也应如此。)

using System;

public class Program
{
    public static void Main(string[] args)
    {
    int a;
    long b;

    a = 0;
    b = 0;

    Console.WriteLine(a.Equals(b)); // False
    Console.WriteLine(a.Equals(0L)); // False
    Console.WriteLine(a.Equals((long)0)); // False
    Console.WriteLine(a.Equals(0)); // True
    Console.WriteLine(a.Equals(a)); // True
    Console.WriteLine(a == b); // True
    Console.WriteLine(a == 0L); // True

    Console.WriteLine();

    Console.WriteLine(b.Equals(a)); // True
    Console.WriteLine(b.Equals(0)); // True
    Console.WriteLine(b.Equals((int)0)); // True
    Console.WriteLine(b.Equals(b)); // True
    Console.WriteLine(b == a); // True
    Console.WriteLine(b == 0); // True
    }
}

这里有两个有趣的点(假设aintblong):

  1. a != b,但b == a;
  2. (a.Equals(b)) != (a == b)
  3. 有没有理由以这种方式实施比较?

    注意:如果它有任何不同,则使用.NET 4.

6 个答案:

答案 0 :(得分:27)

通常,Equals()方法不应该为不同类型的对象返回true。

a.Equals(b)调用int.Equals(object),只能为盒装Int32返回true:

public override bool Equals(Object obj) { 
    if (!(obj is Int32)) {
        return false;
    }
    return m_value == ((Int32)obj).m_value; 
}  
隐含地将b.Equals(a)转换为long.Equals(long)后,

int调用long。 因此,它直接比较两个long,返回true。

为了更清楚地理解,请查看由这个更简单的示例生成的IL(打印True False True):

int a = 0;
long b = 0L;

Console.WriteLine(a == b);
Console.WriteLine(a.Equals(b));
Console.WriteLine(b.Equals(a));

IL_0000:  ldc.i4.0    
IL_0001:  stloc.0     
IL_0002:  ldc.i4.0    
IL_0003:  conv.i8     
IL_0004:  stloc.1     

IL_0005:  ldloc.0     //Load a
IL_0006:  conv.i8     //Cast to long
IL_0007:  ldloc.1     //Load b
IL_0008:  ceq         //Native long equality check
IL_000A:  call        System.Console.WriteLine    //True

IL_000F:  ldloca.s    00            //Load the address of a to call a method on it
IL_0011:  ldloc.1                   //Load b
IL_0012:  box         System.Int64  //Box b to an Int64 Reference
IL_0017:  call        System.Int32.Equals
IL_001C:  call        System.Console.WriteLine    //False

IL_0021:  ldloca.s    01  //Load the address of b to call a method on it
IL_0023:  ldloc.0         //Load a
IL_0024:  conv.i8         //Convert a to Int64
IL_0025:  call        System.Int64.Equals
IL_002A:  call        System.Console.WriteLine    //True

答案 1 :(得分:1)

它们不一样,因为即使是简单类型也是从System.Object继承的 - 它们实际上是对象,而且不同的对象类型,即使具有相同的属性值也不相等。

示例:

你可以拥有一个只有一个属性的Co-Worker对象:Name(字符串)和只有一个属性的伙伴对象:Name(string)

同事大卫和帕纳大卫不一样。它们是不同的对象类型这一事实使它们分开。

在你的情况下,使用.Equals(),你不是在比较值,而是在比较对象。对象不是“0”,它是一个值为零的System.Int32,一个值为零的System.Int64。

基于以下评论中的问题的代码示例:

class CoWorker
{
   public string Name { get; set; }
}

class Partner
{
   public string Name { get; set; }
}

private void button1_Click(object sender, RoutedEventArgs e)
{
   CoWorker cw = new CoWorker();
   cw.Name = "David Stratton";
   Partner p = new Partner();
   p.Name = "David Stratton";

   label1.Content = cw.Equals(p).ToString();  // sets the Content to "false"
}

答案 2 :(得分:0)

还存在缩小或扩大转换的问题。 long零总是等于int零,但不是相反。

当long与int进行比较时,只比较最低有效32位,其余被忽略,因此即使低位匹配,int.Equals(long)操作也不能保证相等。

int a = 0;
long b = 0;

Trace.Assert(a.Equals((int)b));     // True   32bits compared to 32bits
Trace.Assert(a.Equals((long)b));    // False  32bits compared to 64bits (widening)
Trace.Assert(b.Equals((long)a));    // True   64bits compared to 64bits
Trace.Assert(b.Equals((int)a));     // True   64bits compared to 32bits (narrowing)

还要考虑低32位相等的情况,但高位不是。

uint a = 0;
ulong b = 0xFFFFFF000000;
Trace.Assert((uint)a == (uint)b);  // true because of a narrowing conversion
Trace.Assert((ulong)a == (ulong)b);  // false because of a widening conversion

答案 3 :(得分:0)

运算符和方法重载以及转换运算符在编译时进行计算,与在运行时计算的虚方法重写不同。表达式someIntVar.Equals(someNumericQuantity)与表达式someObjectVarThatHoldsAnInt.Equals(someNumericQuantity)完全无关。如果你假装虚拟方法Object.Equals有一个不同的名称(如IsEquivalentTo),并且每个地方都使用虚拟方法替换该名称,这将更加清晰。整数零可以在数值上等于长零,但这并不意味着它们在语义上是等价的。

顺便提一下,EqualsIsEquivalentTo之间的这种意义分离也有助于避免后者定义中的模糊性。可以为任意对象定义有意义的等价关系:如果前者的所有成员的行为始终等同于存储位置X的相应成员,则应将其视为等同于存储位置Y。后者,确定XY是否引用同一对象的唯一方法是使用Reflection或ReferenceEquals。尽管1.0m.Equals(1.00m)是,而且应该是真的,1.0m.IsEquivalentTo(1.00m)应该是假的。不幸的是,对象等效测试方法和Decimal数字相等测试方法使用相同的名称导致Microsoft将前者定义为后者。

答案 4 :(得分:-1)

因为Equals比较对象而a和b对象是不同的。它们具有相同的值,但作为对象不同

此链接可以为您提供帮助:http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

答案 5 :(得分:-1)

C#不进行自动投射。等于函数比较类型和值。很像在JS中的===。