双重有什么区别?和int?for .Equals比较?

Ite*_*ria 8 c# nullable

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

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

Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true
Run Code Online (Sandbox Code Playgroud)

我不明白为什么一个表达式会使我真实而另一个表达错误.它们似乎相同.

Eri*_*ert 14

你发现这令人困惑是完全正确的.这是一团糟.

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

    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));
Run Code Online (Sandbox Code Playgroud)

所有这些打印都是True,除了那些我记录为打印错误的.

我现在将分析这些案例.

首先要注意的是==操作员总是说True.这是为什么?

非可空的语义==如下:

int == int -- compare the integers
int == double -- convert the int to double, compare the doubles
double == int -- same
double == double -- compare the doubles
Run Code Online (Sandbox Code Playgroud)

所以在每个非可空的情况下,整数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/ - the问题是==基于两个操作数编译时类型来解决其含义.但Equals解决的基础上运行时类型的的操作数(接收器),但编译时间类型的的右侧操作数(参数),这就是为什么事情出轨.

让我们先看一下是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?是不可兑换成双倍的,但它是object.所以我们打电话double.Equals(object)和打包int,现在它不相等.

怎么样nd.Equals(object)?其语义是:

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

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

最后,通过类似的逻辑,我们看到为什么int.Equals(object)给出它所具有的行为.它检查它的参数是否为盒装int,如果不是,则返回false.因此i.Equals(d)是错误的.在i不能转换到加倍,并且d不能被转换为int.

这是一个巨大的混乱.我们希望平等是一种等价关系,而不是!相等关系应该具有以下属性:

  • 反身性:事物就等于自己.这在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为空的情况.什么时候得到与非可空接收器相同的结果,何时得到不同的结果?