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)
在 x
是 double
或 double?
时有效,但在 int
或 [=18= 时无效]. (虽然有趣,当然 (default(double?)).Equals(default(int?))
会是真的,因为它们都是空的!)
最后,通过类似的逻辑,我们明白了为什么 int.Equals(object)
给出了它所具有的行为。它检查它的参数是否是一个装箱的 int,如果不是,那么它 returns false。因此 i.Equals(d)
是错误的。 i
不能转为double,d
不能转为int。
这真是一团糟。我们希望 equality 是 equivalence 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;
}
我有一个很奇怪的情况,我不明白。下面是简化的案例:
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)
在 x
是 double
或 double?
时有效,但在 int
或 [=18= 时无效]. (虽然有趣,当然 (default(double?)).Equals(default(int?))
会是真的,因为它们都是空的!)
最后,通过类似的逻辑,我们明白了为什么 int.Equals(object)
给出了它所具有的行为。它检查它的参数是否是一个装箱的 int,如果不是,那么它 returns false。因此 i.Equals(d)
是错误的。 i
不能转为double,d
不能转为int。
这真是一团糟。我们希望 equality 是 equivalence 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;
}