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);
}