我遇到了一个非常奇怪的情况,我不理解。以下是简化的情况:
double? d = 2;
int? i = 2;
Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true
我不明白为什么一个表达式会返回true,而另一个却是false。它们看起来一模一样。
我遇到了一个非常奇怪的情况,我不理解。以下是简化的情况:
double? d = 2;
int? i = 2;
Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true
我不明白为什么一个表达式会返回true,而另一个却是false。它们看起来一模一样。
你完全正确,发现这个问题确实很令人困惑。这真是一团糟。
让我们通过更多的例子来清晰地说明发生了什么,然后我们将推导出正确的规则。让我们扩展您的程序以考虑所有这些情况:
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));
除了我标记为输出false的那些之外,所有这些都打印为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
因此,在每种非可空情况下,整数2等于双精度2.0,因为int 2转换为double 2.0,并且比较结果为true。
可空类型 ==
的语义如下:
因此,对于可空比较, int? == double?
, int? == double
等,我们总是回退到非可空情况,将 int?
转换为 double
,并在双精度中进行比较。 因此这些也都是正确的。
现在我们来看 Equals
,这就是事情变得混乱的地方。
这里有一个根本性的设计问题,我在2009年写过: https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/ -- 问题在于 ==
的意义是基于两个操作数的编译时类型确定的。但是, Equals
是基于左操作数(接收器)的运行时类型解析的,但是基于右操作数(参数)的编译时类型,这就是为什么事情会出错的原因。
让我们从看 double.Equals(object)
开始。如果调用 Equals(object)
的接收器是double
,那么如果参数不是装箱的double,则认为它们不相等。也就是说,Equals
要求类型匹配,而 ==
要求类型可转换为公共类型。
我再重复一遍。与 ==
不同, double.Equals
并不尝试将其参数转换为double。 它只检查它是否已经是double,如果不是,则表示它们不相等。
那么这就解释了为什么d.Equals(i)
是false...但是...等等,它在上面不是false!这是为什么呢?
double.Equals
是重载的!上面,我们实际上正在调用double.Equals(double)
,你猜对了——在调用之前将int转换为double! 如果我们说d.Equals((object)i))
,那么它将是false。
好的,我们知道为什么double.Equals(int)
是true——因为int被转换为double。
我们也知道为什么double.Equals(int?)
返回false。 int?
不能转换为double,但可以转换为object
。 因此,我们调用double.Equals(object)
并装箱int
,现在它不相等。
nd.Equals(object)
呢? 它的语义是:
d.Equals(object)
的非可空语义因此,现在我们知道nd.Equals(x)
适用于x
为double
或double?
,但如果是int
或int?
则不适用。 (有趣的是,当然,(default(double?))。Equals(default(int?))
将是true,因为它们都为null!)
最后,通过类似的逻辑,我们看到int.Equals(object)
产生其具有的行为方式。 它检查其参数是否为包含的int,如果不是,则返回false。 因此,i.Equals(d)
为false。 i
不能转换为double,d
不能转换为int。
这是一团糟。 我们希望等式成为一个等价关系,但它并不是! 等式关系应具有以下属性:
==
是正确的,但在A.Equals(B)
中不正确,如我们所见。因此,在所有级别上都很混乱。 ==
和 Equals
具有不同的调度机制并给出不同的结果,它们都不是等价关系,并且一直令人困惑。 对于让您遇到这种情况,我表示歉意,但我来到这里时就已经很混乱了。
有关C#中等式恶劣的另一种看法,请参见我对后悔的语言决策清单中的第9项:http://www.informit.com/articles/article.aspx?p=2425867
奖励练习:针对可空接收者,重复上述分析,但为x?.Equals(y)
的情况。 何时获得与非可空接收者相同的结果,何时获得不同的结果?
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;
}
Nullable<T>.Equals(object)
检查是否为空,然后将其委托给基础类型 T
上的 object.Equals(object)
方法,(2) double.Equals()
有多种重载形式。 - Eric Lippert