反编译器如何识别编译常量?

How is a decompiler able to recognize a compiled constant?

我正在使用 ILSpy 反编译 .Net 程序集并查看代码。当我浏览 WindowsBase.dllSystem.Windows.Vector.AngleBetween(Vector, Vector) 的代码时,我偶然发现了一些奇怪的东西。

这是函数的完整代码:

public static double AngleBetween(Vector vector1, Vector vector2)
{
    double y = vector1._x * vector2._y - vector2._x * vector1._y;
    double x = vector1._x * vector2._x + vector1._y * vector2._y;
    return Math.Atan2(y, x) * (180.0 / Math.PI);
}

显然 ILSpy 可以识别 Math.PI,这是一个常量。

这就是 Microsoft Docs 关于 C# 中常量的说法:

In fact, when the compiler encounters a constant identifier in C# source code, it substitutes the literal value directly into the intermediate language (IL) code that it produces.

基于此,ILSpy 所做的似乎是不可能的。

注意:即使 "Use variable names from debug symbols, if available" 和 "Show info from debug symbols, if available" 设置中的选项未选中。

LINQPad 中的快速测试显示生成的 IL 代码存在一些差异(从 here 复制的 PI 常量值)。

来源:

void Main()
{
    double a = Math.PI;
    double b = 3.14159265358979;
}

IL:

IL_0000:  nop         
IL_0001:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_000A:  stloc.0     // a
IL_000B:  ldc.r8      11 2D 44 54 FB 21 09 40 
IL_0014:  stloc.1     // b
IL_0015:  ret   

在常量和文字值之间生成的 IL 代码中似乎确实存在细微差别,但我还不确定它到底意味着什么。


以上值来自MSDN documentation seem to contradict information from the .NET Reference Source(见评论)。从源代码调整后,IL 是相同的。

来源:

void Main()
{
    var a = Math.PI;
    var b = 3.14159265358979323846;
}

IL:

IL_0000:  nop         
IL_0001:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_000A:  stloc.0     // a
IL_000B:  ldc.r8      18 2D 44 54 FB 21 09 40 
IL_0014:  stloc.1     // b
IL_0015:  ret   

顺便说一句,似乎有一个 open issue 来解决文档/源代码不一致的问题。

如您在此 ILSpy issue and the corresponding pull request 中所见,这是专门针对 Math.PI.

等众所周知的值实施(硬编码)的

来自 GitHub 问题:

I suppose to calculate pi coefficient by following way: c = Math.PI / constant. If we getting "good" value (equal exactly to 1.0, 2.0, 0.5, 1/180 and so on), we simply replacing it with symbolic expression (Math.PI, Math.PI * 2, Math.PI / 2, Math.PI / 180 and so on).