double 和有什么不一样?和诠释?对于 .Equals 比较?

What is the difference between double? and int? for .Equals comparisons?

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

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

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

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

您认为这令人困惑是完全正确的。一团糟。

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

    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。这是为什么?

不可空==的语义如下:

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

所以在所有不可为 null 的情况下,整数 2 等于 double 2.0,因为 int 2 被转换为 double 2.0,比较结果为真。

可空 == 的语义是:

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

所以,我们再次看到,对于可为空的比较,int? == double?int? == double,等等,我们总是回退到不可为空的情况,将 int?double,并以双打形式进行比较。所以这些也是真的。

现在我们来到 Equals,这就是事情变得混乱的地方。

这里有一个基本的设计问题,我在2009年写过:https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/——问题是==的意思是根据compile解决的两个操作数的时间类型。但是Equals是根据left操作数(接收者)的运行时间类型解析的,但是正确操作数(参数)的编译时类型,这就是为什么事情会偏离rails.

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

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

那就解释了为什么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)
  • 的不可空语义

所以现在我们知道为什么 nd.Equals(x)xdoubledouble? 时有效,但在 int 或 [=18= 时无效]. (虽然有趣,当然 (default(double?)).Equals(default(int?)) 会是真的,因为它们都是空的!)

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

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

  • 自反性:事物等于自身。这在 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

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

看起来答案在每个类型的 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;
}