13

在看到double.Nan == double.NaNC# 中的总是 false 之后,我开始好奇相等是如何在幕后实现的。所以我使用 Resharper 反编译了 Double 结构,这是我发现的:

public struct Double : IComparable, IFormattable, IConvertible, IComparable<double>, IEquatable<double>
{
    // stuff removed...

    public const double NaN = double.NaN;

    // more stuff removed...
}

这似乎表明 structDouble声明了一个根据这个特殊的小写字母定义的常量double,尽管我一直认为这两者是完全同义的。更重要的是,如果我在小写双精度上转到实施,Resharper 只需将我滚动到文件顶部的声明。同样,跳转到小写字母的实现NaN只是将我带到行前面的常量声明!

所以我试图理解这个看似递归的定义。这只是反编译器的产物吗?也许是 Resharper 的限制?或者这个小写双精度实际上是完全不同的野兽 - 代表 CLR/CTS 较低级别的东西?

NaN真正来自哪里?

4

2 回答 2

15

注意查看反编译的代码,特别是如果它是用于内置的。这里的实际 IL(至少对于 .NET 4.5)是:

.field public static literal float64 NaN = float64(NaN)
{
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
}

即,这是通过NaN令牌在 IL 中直接处理的。

但是,因为它是一个constliteral在 IL 中),它会被“烧入”调用站点;其他任何使用的地方double.NaN将使用. 同样,例如,如果我这样做:float64(NaN)

const int I = 2;
int i = I;
int j = 2;

这两个分配在最终的 IL 中看起来是相同的(它们都是ldc.i4.2)。

正因为如此,大多数反编译器会识别 IL 模式NaN并用语言的double.NaN. 但这并不意味着代码本身就是递归的。他们可能只是没有检查“但它是 double.NaN 本身吗?”。归根结底,这只是一种特殊情况,其中float64(NaN)是 IL 中公认的值。

顺便说一句,反射器将其反编译为:

[__DynamicallyInvokable]
public const double NaN = (double) 1.0 / (double) 0.0;

这再次并不意味着这是事实 :p 只是这可能具有相同的最终结果。

于 2013-05-22T14:03:19.377 回答
8

到目前为止,您可以获得的 .NET 程序集的最佳来源是用于构建它们的实际源代码。击败任何反编译器的准确性,注释也非常有用。下载参考源

然后您还会看到,它Double.NaN并没有像 Marc 假设的那样在 IL 中定义,它实际上是在 C# 源代码文件中。net/clr/bcl/system/double.cs源代码文件显示了真正的声明:

  public const double NaN = (double)0.0 / (double)0.0;

它利用了 C# 编译器在编译时评估常量表达式的优势。或者开玩笑地说,NaN 是由 C++ 编译器定义的,因为这是用于编写 C# 编译器的语言;)

于 2013-05-22T16:13:00.547 回答