我刚刚在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
}
}
这里有两个有趣的点(假设a
是int
而b
是long
):
a != b
,但b == a
; (a.Equals(b)) != (a == b)
有没有理由以这种方式实施比较?
注意:如果它有任何不同,则使用.NET 4.
答案 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
),并且每个地方都使用虚拟方法替换该名称,这将更加清晰。整数零可以在数值上等于长零,但这并不意味着它们在语义上是等价的。
顺便提一下,Equals
和IsEquivalentTo
之间的这种意义分离也有助于避免后者定义中的模糊性。可以为任意对象定义有意义的等价关系:如果前者的所有成员的行为始终等同于存储位置X
的相应成员,则应将其视为等同于存储位置Y
。后者,确定X
和Y
是否引用同一对象的唯一方法是使用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中的===。