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

我刚刚在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)

是否有任何理由以这种方式实施比较?

注意:如果它有任何区别,则使用.NET 4。

通常,对于不同类型的对象, 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)在将int隐式转换为int之后调用long.Equals(long)
因此,它直接比较两个long s,返回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 

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

例:

你可以有一个只有一个属性的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" } 

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

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

还存在缩小或扩大转换的问题。 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 

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

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

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