【问题标题】:What is the difference between double? and int? for .Equals comparisons?双倍有什么区别?和int?对于 .Equals 比较?
【发布时间】:2018-09-26 18:03:53
【问题描述】:

我有一个非常奇怪的情况,我不明白。下面是简化的案例:

double? d = 2;
int? i = 2;

Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true

我不明白为什么一个表达式会让我觉得是真的而另一个是假的。它们看起来一模一样。

【问题讨论】:

  • 它们可能看起来与文本相同,但它们的二进制表示非常不同。请参阅this answer 了解更多信息
  • 这个问题不是关于如何比较双精度数和整数的问题的重复。这里的问题是,将 nullable double 与 int 进行比较会给出一个答案,将 same double 与 non-nullable 进行比较会给出不同的答案。
  • 对这个问题的反对意见是没有根据的。问题很明确,C# 的哪些规则导致了这种行为,这绝不是显而易见的。此行为是 C# 中严重设计错误的一个示例; C# 处理平等的方式很糟糕。
  • int32.equals 如果其他值不是 int32 referencesource.microsoft.com/#mscorlib/system/… 将返回 false

标签: c# nullable


【解决方案1】:

你觉得这很令人困惑是完全正确的。真是一团糟。

让我们首先通过查看更多示例清楚地说明会发生什么,然后我们将推断出此处应用的正确规则。让我们扩展您的程序以考虑所有这些情况:

    double d = 2;
    double? nd = d;
    int i = 2;
    int? ni = i;
    Console.WriteLine(d == d);
    Console.WriteLine(d == nd);
    Console.WriteLine(d == i);
    Console.WriteLine(d == ni);
    Console.WriteLine(nd == d);
    Console.WriteLine(nd == nd);
    Console.WriteLine(nd == i);
    Console.WriteLine(nd == ni);
    Console.WriteLine(i == d);
    Console.WriteLine(i == nd);
    Console.WriteLine(i == i);
    Console.WriteLine(i == ni);
    Console.WriteLine(ni == d);
    Console.WriteLine(ni == nd);
    Console.WriteLine(ni == i);
    Console.WriteLine(ni == ni);
    Console.WriteLine(d.Equals(d));
    Console.WriteLine(d.Equals(nd));
    Console.WriteLine(d.Equals(i));
    Console.WriteLine(d.Equals(ni)); // False
    Console.WriteLine(nd.Equals(d));
    Console.WriteLine(nd.Equals(nd));
    Console.WriteLine(nd.Equals(i)); // False
    Console.WriteLine(nd.Equals(ni)); // False
    Console.WriteLine(i.Equals(d)); // False
    Console.WriteLine(i.Equals(nd)); // False
    Console.WriteLine(i.Equals(i)); 
    Console.WriteLine(i.Equals(ni));
    Console.WriteLine(ni.Equals(d)); // False
    Console.WriteLine(ni.Equals(nd)); // False
    Console.WriteLine(ni.Equals(i)); 
    Console.WriteLine(ni.Equals(ni));

所有这些都打印 True,除了我标注为打印 false 的那些。

我现在将对这些案例进行分析。

首先要注意的是== 运算符总是说True。这是为什么呢?

不可为空的==的语义如下:

int == int -- compare the integers
int == double -- convert the int to double, compare the doubles
double == int -- same
double == double -- compare the doubles

所以在每个不可为空的情况下,整数 2 都等于 double 2.0,因为 int 2 被转换为 double 2.0,并且比较为真。

可空==的语义是:

  • 如果两个操作数都为 null,则它们相等
  • 如果一个为空而另一个不为空,则它们不相等
  • 如果两者都不为空,则回退到上述不可为空的情况。

因此,我们再次看到,对于可空比较 int? == double?int? == double 等,我们总是退回到不可空情况,将 int? 转换为 double,然后执行双打比较。因此,这些也是正确的。

现在我们来到Equals,这就是事情变得一团糟的地方。

这里有一个基本的设计问题,我在 2009 年写过:https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/——问题是 == 的含义是基于 两个操作数的编译时间类型。但是Equals是根据left操作数(接收方)的运行时类型来解析的,但是编译时类型正确的操作数(参数),这就是事情出轨的原因。

让我们先来看看double.Equals(object) 做了什么。如果对Equals(object) 的调用的接收者是double,那么如果参数不是装箱的双精度,则认为它们不相等。也就是说,Equals 要求类型匹配,而== 要求类型可转换为通用类型

我再说一遍。 double.Equals 确实 尝试将其参数转换为双精度,这与== 不同。它只是检查它是否已经双倍,如果不是,那么它说它们不相等。

这就解释了为什么d.Equals(i) 是假的......但是......等一下,上面的不是假的!这是什么原因?

double.Equals 已超载!上面我们实际上调用了double.Equals(double),它——你猜对了——在调用之前将int转换为double!如果我们说d.Equals((object)i)),那将是错误的。

好的,所以我们知道为什么double.Equals(int) 为真——因为 int 被转换为 double。

我们也知道为什么double.Equals(int?) 是假的。 int? 不能转换为 double,但可以转换为 object。所以我们调用double.Equals(object) 并将int 框起来,现在它不相等。

nd.Equals(object) 呢?其语义是:

  • 如果接收者为空且参数为空,则它们相等
  • 如果接收者不为空,则遵循d.Equals(object) 的不可空语义

所以现在我们知道为什么 xdoubledouble?nd.Equals(x) 有效,但如果它是 intint? 则无效。 (虽然有趣的是,(default(double?)).Equals(default(int?)) 当然是真的,因为它们都是空的!)

最后,通过类似的逻辑,我们看到了为什么int.Equals(object) 给出了它所具有的行为。它检查它的参数是否是一个装箱的 int,如果不是,则返回 false。因此i.Equals(d) 是错误的。 i 不能转为 double,d 不能转为 int。

这是一团糟。我们希望 equality 是一个 等价关系,但事实并非如此!相等关系应具有以下属性:

  • 自反性:事物与自身相等。这通常在 C# 中是正确的,但也有一些例外。
  • 对称性:如果 A 等于 B,则 B 等于 A。正如我们所见,C# 中的 == 是这样,但 A.Equals(B) 不是这样。
  • 传递性:如果 A 等于 B 且 B 等于 C,则 A 也等于 C。在 C# 中绝对不是这种情况。

所以,它在各个层面上都是一团糟。 ==Equals 有不同的调度机制,给出的结果也不同,都不是等价关系,一直很混乱。很抱歉让你陷入这种混乱,但当我到达时,情况就很糟糕了。

关于为什么平等在 C# 中很糟糕的看法略有不同,请参阅我的令人遗憾的语言决定列表中的第 9 项,此处:http://www.informit.com/articles/article.aspx?p=2425867

BONUS EXERCISE:重复上述分析,但对于 x?.Equals(y) 的情况,x 可以为空。什么时候得到与不可为空的接收者相同的结果,什么时候得到不同的结果?

【讨论】:

    【解决方案2】:

    看起来答案在每种类型的Equals 方法的源代码中。如果类型不匹配,则它们不相等。

    https://referencesource.microsoft.com/#mscorlib/system/double.cs,147

    // True if obj is another Double with the same value as the current instance.  This is
    // a method of object equality, that only returns true if obj is also a double.
    public override bool Equals(Object obj) {
        if (!(obj is Double)) {
            return false;
        }
        double temp = ((Double)obj).m_value;
        // This code below is written this way for performance reasons i.e the != and == check is intentional.
        if (temp == m_value) {
            return true;
        }
        return IsNaN(temp) && IsNaN(m_value);
    }
    

    https://referencesource.microsoft.com/#mscorlib/system/int32.cs,72

    public override bool Equals(Object obj) {
        if (!(obj is Int32)) {
            return false;
        }
        return m_value == ((Int32)obj).m_value;
    }
    

    【讨论】:

    • 谢谢,答案很明确。而且,非常出乎意料。编辑:实际上我必须收回,我不确定这个答案是否适用于可为空的
    • @Iteria:这个答案大部分都在那里。您必须知道的另外两件事是:(1)Nullable<T>.Equals(object) 检查是否为空,然后在基础类型T 上遵循object.Equals(object),以及(2)double.Equals() 有重载。
    • 啊,我明白了。在这个答案和你上面的答案之间。我想我有。
    猜你喜欢
    • 1970-01-01
    • 2010-12-11
    • 2011-11-23
    • 2011-12-02
    • 1970-01-01
    • 2022-12-04
    相关资源
    最近更新 更多