C#:为什么下面的比较表明 0 != 0
C#: Why does the following comparison indicate that 0 != 0
我最近在一些比较代码中偶然发现了一个有趣的错误,其中两个对象的 属性 都等于 0.0m。当 属性 转换为 int 并进行比较时,比较永远不会相等。转载如下:
采用抽象 A 和两个实现 B 和 C:
public abstract class A
{
public decimal MyProp { get; set; }
}
public class B : A
{
}
public class C : A
{
}
抽象定义了几个 public 属性,主要但不完全是 decimal。所有 public 属性始终是原语。具体子类型表示从两个不同数据源获得的这种抽象。当且仅当它们的所有 public 属性都相等时,类型 A 的两个对象才被视为相等。一个警告:所有十进制属性在比较之前都应转换为 int,使用默认舍入行为 (MidpointRounding.ToEven)。这导致了以下比较代码:
private static bool Compare(A a1, A a2)
{
var propertiesList = typeof(A).GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
foreach (var propertyInfo in propertiesList)
{
var value1 = propertyInfo.GetValue(a1);
var value2 = propertyInfo.GetValue(a2);
if (propertyInfo.PropertyType == typeof(decimal))
{
value1 = Convert.ToInt32(value1);
value2 = Convert.ToInt32(value2);
}
// debugger confirms that value1 is 0 and value2 is 0
if (value1 != value2)
{
// yet these lines are always called
Console.WriteLine("The two A's are not equal");
return false;
}
}
return true;
}
此代码旨在编写为:
A1.MyProp A2.MyProp Equal?
---------------------------------
0.0m 0.0m Yes
0.6m 1.4m Yes
1.5m 2.5m Yes
2.5m 3.5M No
但是,如以下控制台应用所示,第一个用例(0.0m 和 0.0m)始终失败:
private static void Main(string[] args)
{
var b = new B() { MyProp = 0.0m };
var c = new C() { MyProp = 0.0m };
// always false
var result = Compare(b, c);
}
任何人都可以帮忙指出比较代码中的错误吗?
那是因为 ==
在 object
上确实引用了相等性。
改用Equals
:
// debugger confirms that value1 is 0 and value2 is 0
if (!value1.Equals(value2))
{
Console.WriteLine("The two A's are not equal");
return false;
}
要使其空安全,您还应该首先检查 null
:
if((value1 == null && value2) != null || (value1 == null && value2 != null) || !value1.Equals(value2))
或按照评论中的建议使用静态 object.Equals
:
if (!object.Equals(value1, value2))
{
Console.WriteLine("The two A's are not equal");
return false;
}
我建议使用 Equals
而不是 ==
因为你正在处理对象
Equals 方法只是在 System.Object 中定义的一个虚拟方法,并被 类 选择这样做的任何一个覆盖。 == 运算符是一个可以被 类 重载的运算符,但通常具有相同的行为。
对于 == 没有被重载的引用类型,它比较两个引用是否引用同一个对象——这正是 System.Object.
中 Equals 的实现所做的。
默认情况下,值类型不为 == 提供重载。但是,框架提供的大多数值类型都提供了它们自己的重载。值类型的 Equals 的默认实现由 ValueType 提供,并使用反射进行比较,这使得它比通常的特定类型实现慢得多。此实现还对正在比较的两个值中的引用对调用 Equals。
但是,在正常使用(您不太可能经常定义自己的值类型)中,这两种比较类型之间的主要区别是多态性。运算符被重载,而不是被覆盖,这意味着除非编译器知道调用更具体的版本,否则它只会调用标识版本。为了说明这一点,这里有一个例子:
using System;
public class Test
{
static void Main()
{
// Create two equal but distinct strings
string a = new string(new char[] {'h', 'e', 'l', 'l', 'o'});
string b = new string(new char[] {'h', 'e', 'l', 'l', 'o'});
Console.WriteLine (a==b);
Console.WriteLine (a.Equals(b));
// Now let's see what happens with the same tests but
// with variables of type object
object c = a;
object d = b;
Console.WriteLine (c==d);
Console.WriteLine (c.Equals(d));
}
}
结果是:
正确
真的
错误的
真
@MarcinJuraszek 是对的,他提供了关于为什么 !=
总是 returns 正确的确切答案。
我只是想说明运算符重载的存在,并且它可能在某个时候有意义。
这是来自 MSDN doc 的示例。
//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.x == b.x && a.y == b.y && a.z == b.z;
}
public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
return !(a == b);
}
我最近在一些比较代码中偶然发现了一个有趣的错误,其中两个对象的 属性 都等于 0.0m。当 属性 转换为 int 并进行比较时,比较永远不会相等。转载如下:
采用抽象 A 和两个实现 B 和 C:
public abstract class A
{
public decimal MyProp { get; set; }
}
public class B : A
{
}
public class C : A
{
}
抽象定义了几个 public 属性,主要但不完全是 decimal。所有 public 属性始终是原语。具体子类型表示从两个不同数据源获得的这种抽象。当且仅当它们的所有 public 属性都相等时,类型 A 的两个对象才被视为相等。一个警告:所有十进制属性在比较之前都应转换为 int,使用默认舍入行为 (MidpointRounding.ToEven)。这导致了以下比较代码:
private static bool Compare(A a1, A a2)
{
var propertiesList = typeof(A).GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
foreach (var propertyInfo in propertiesList)
{
var value1 = propertyInfo.GetValue(a1);
var value2 = propertyInfo.GetValue(a2);
if (propertyInfo.PropertyType == typeof(decimal))
{
value1 = Convert.ToInt32(value1);
value2 = Convert.ToInt32(value2);
}
// debugger confirms that value1 is 0 and value2 is 0
if (value1 != value2)
{
// yet these lines are always called
Console.WriteLine("The two A's are not equal");
return false;
}
}
return true;
}
此代码旨在编写为:
A1.MyProp A2.MyProp Equal?
---------------------------------
0.0m 0.0m Yes
0.6m 1.4m Yes
1.5m 2.5m Yes
2.5m 3.5M No
但是,如以下控制台应用所示,第一个用例(0.0m 和 0.0m)始终失败:
private static void Main(string[] args)
{
var b = new B() { MyProp = 0.0m };
var c = new C() { MyProp = 0.0m };
// always false
var result = Compare(b, c);
}
任何人都可以帮忙指出比较代码中的错误吗?
那是因为 ==
在 object
上确实引用了相等性。
改用Equals
:
// debugger confirms that value1 is 0 and value2 is 0
if (!value1.Equals(value2))
{
Console.WriteLine("The two A's are not equal");
return false;
}
要使其空安全,您还应该首先检查 null
:
if((value1 == null && value2) != null || (value1 == null && value2 != null) || !value1.Equals(value2))
或按照评论中的建议使用静态 object.Equals
:
if (!object.Equals(value1, value2))
{
Console.WriteLine("The two A's are not equal");
return false;
}
我建议使用 Equals
而不是 ==
因为你正在处理对象
Equals 方法只是在 System.Object 中定义的一个虚拟方法,并被 类 选择这样做的任何一个覆盖。 == 运算符是一个可以被 类 重载的运算符,但通常具有相同的行为。
对于 == 没有被重载的引用类型,它比较两个引用是否引用同一个对象——这正是 System.Object.
中 Equals 的实现所做的。默认情况下,值类型不为 == 提供重载。但是,框架提供的大多数值类型都提供了它们自己的重载。值类型的 Equals 的默认实现由 ValueType 提供,并使用反射进行比较,这使得它比通常的特定类型实现慢得多。此实现还对正在比较的两个值中的引用对调用 Equals。
但是,在正常使用(您不太可能经常定义自己的值类型)中,这两种比较类型之间的主要区别是多态性。运算符被重载,而不是被覆盖,这意味着除非编译器知道调用更具体的版本,否则它只会调用标识版本。为了说明这一点,这里有一个例子:
using System;
public class Test
{
static void Main()
{
// Create two equal but distinct strings
string a = new string(new char[] {'h', 'e', 'l', 'l', 'o'});
string b = new string(new char[] {'h', 'e', 'l', 'l', 'o'});
Console.WriteLine (a==b);
Console.WriteLine (a.Equals(b));
// Now let's see what happens with the same tests but
// with variables of type object
object c = a;
object d = b;
Console.WriteLine (c==d);
Console.WriteLine (c.Equals(d));
}
}
结果是:
正确 真的 错误的 真
@MarcinJuraszek 是对的,他提供了关于为什么 !=
总是 returns 正确的确切答案。
我只是想说明运算符重载的存在,并且它可能在某个时候有意义。
这是来自 MSDN doc 的示例。
//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.x == b.x && a.y == b.y && a.z == b.z;
}
public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
return !(a == b);
}