4

为什么const用法在编译时被它们的值替换,但仍然包含在程序集中?至少这是 IL DASM 和 Reflector 所展示的。

目前我正在使用const定义许多幻数和字符串,以便更容易地更改代码而不影响运行时性能。

现在我知道这不会影响使用的内存,但它仍然会影响编译的程序集大小,这对于手机应用程序来说至关重要。

另一个缺点是其他人在查看反汇编代码时更容易理解幻数。

我真的很感兴趣为什么编译器(Mono 和 .NET)会这样做?

4

2 回答 2

6

此行为在ECMA-335 标准中指定(.NET 和 Mono 都是其中的实现)。引用第 II.22.9 节“常量”:

请注意,Constant信息不会直接影响运行时行为,尽管它通过反射可见(因此可用于实现诸如 提供的功能System.Enum.ToString)。编译器在导入元数据时在编译时检查此信息,但常量本身的值(如果使用)会嵌入到编译器发出的 CIL 流中。没有 CIL 指令可以Constant在运行时访问该表。

也就是说,const值是“内联的”(可能是因为它们可以,并且出于性能原因),但仍然保存在元数据中,以便编译器和工具可以检查它们。

如果没有为字段的 -ness 发出元数据const,则会产生以下后果(可能还有其他后果——这只是两个示例):

  • 编译器或 Reflector 等工具无法再区分常规字段和const字段。
  • 如果您使用 来检查一个字段System.Reflection,您将无法再使用该FieldInfo.IsLiteral属性
于 2013-04-06T09:53:49.060 回答
2

增加的程序集大小基本上是由于 C# 编译器发出了关于const-ness 的额外元数据。


您希望从这个简短的程序中得到什么输出?

class Program
{
    public const int C = 0;
    public       int F = 0;

    static void Main(string[] args)
    {
        foreach (FieldInfo field in typeof(Program).GetFields())
        {
            Console.WriteLine("{0}: IsLiteral = {1}", field.Name, field.IsLiteral);
        }
    }
}

实际输出为:

C: IsLiteral = True
F: IsLiteral = False

与 C# 源代码中的声明完全匹配:两个字段,其中一个const.

现在假设 C# 编译器决定不发出Constant元数据。那么输出将是:

C: IsLiteral = False
F: IsLiteral = False

与 C# 源代码相比,这显然是不正确的,因为现在这两个字段都显示为非const.

最后,假设 C# 编译器决定根本不发出任何元数据C(因为它无论如何“内联”了该字段的值):

F: IsLiteral = False

这也是不正确的,因为反射不再报告 C# 源代码中明显存在的字段的存在。至少对我来说,那将是一个令人大开眼界的好时机。

这些反例应该清楚地说明为什么即使对于const字段也发出完整的元数据是一件好事。

于 2013-04-06T10:58:43.523 回答