我最近在一些比较代码中偶然发现了一个有趣的错误,其中两个对象的属性都等于0.0m。当该属性转换为int并进行比较时,比较永远不会相等。复制如下:
采用抽象A和两个实现,B和C:
public abstract class A
{
public decimal MyProp { get; set; }
}
public class B : A
{
}
public class C : A
{
}
抽象定义了几个公共属性,主要是但不完全是十进制。所有公共属性都是原始属性。具体的子类型表示从两个不同的数据源获得的这种抽象。当且仅当它们的所有公共属性相等时,A类型的两个对象才被认为是相等的。需要注意的是: 使用默认的舍入行为(MidpointRounding.ToEven) ,所有十进制属性都应在比较前转换为int。这导致了以下比较代码:
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);
}
有人可以看一下并指出比较代码中的错误吗?
答案 0 :(得分:8)
因为==
上的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;
}
答案 1 :(得分:1)
我建议您使用Equals
代替==
,因为您正在处理对象
Equals方法只是在System.Object中定义的虚拟方法,并且被选择执行的任何类重写。 ==运算符是一个运算符,可以通过类重载,但通常具有标识行为。
对于尚未重载==的引用类型,它会比较两个引用是否引用同一个对象 - 这正是Equals在System.Object中的实现。
默认情况下,值类型不会为==提供重载。但是,框架提供的大多数值类型都提供了自己的重载。值类型的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));
}
}
结果是:
真 真正 假 真
答案 2 :(得分:0)
@MarcinJuraszek是对的,他提供了确切的答案,为什么!=
总是在你的情况下返回true。
我只想知道运算符过载是否存在,并且它可能在某个时候有意义。
以下是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);
}