C#:为什么以下比较表明0!= 0

时间:2015-04-20 19:47:36

标签: c# reflection equality autoboxing

我最近在一些比较代码中偶然发现了一个有趣的错误,其中两个对象的属性都等于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);
}

有人可以看一下并指出比较代码中的错误吗?

3 个答案:

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