澄清引用字符串的IL生成代码

时间:2018-12-13 17:41:31

标签: c# .net cil

我今天正在执行一些重构,我注意到我不明白的一件奇怪的事……或者更好的是,我同意我在网上找到的内容,但仍然有一些疑问。

请考虑以下简单示例

 class Program
{
    public static readonly string a = "a";
    public const string b = "b";
    static void Main(string[] args)
    {

        Console.WriteLine(a);

        Console.WriteLine(b);
    }
}

现在,如果我查看生成的IL代码(通过resharp通过IL浏览器获取)

我看到以下代码

.method private hidebysig static void 
Main(
  string[] args
) cil managed 
{
 .entrypoint
.maxstack 8

// [16 13 - 16 34]
IL_0000: ldsfld       string ConsoleApp4.Program::a
IL_0005: call         void [mscorlib]System.Console::WriteLine(string)

// [18 13 - 18 34]
IL_000a: ldstr        "b"
IL_000f: call         void [mscorlib]System.Console::WriteLine(string)

// [19 9 - 19 10]
IL_0014: ret          

 } // end of method Program::Main

 .method public hidebysig specialname rtspecialname instance void 
.ctor() cil managed 
{
 .maxstack 8

IL_0000: ldarg.0      // this
IL_0001: call         instance void [mscorlib]System.Object::.ctor()
IL_0006: ret          

 } // end of method Program::.ctor

 .method private hidebysig static specialname rtspecialname void 
.cctor() cil managed 
 {
.maxstack 8

// [11 9 - 11 47]
IL_0000: ldstr        "a"
IL_0005: stsfld       string ConsoleApp4.Program::a
IL_000a: ret          

 } // end of method Program::.cctor
  } // end of class ConsoleApp4.Program

对于静态字符串而言,它的行为符合我的预期。 相反,对于const,它会在堆栈上加载一个新值...实际上是看它说的ldstr操作码here

  

向存储在元数据中的字符串文字推送新的对象引用

我已经读过here

  

现在,无论在代码中引用myInt的什么地方,MSIL都不必加载“ ldloc.0”来从变量中获取值,而是只需加载将其硬编码到MSIL中的常量值即可。因此,使用常数通常在性能和内存方面具有较小的优势,但是,要使用它们,您必须在编译时具有变量的值,并且在编译时必须具有对该常量的任何引用,即使它们在一个不同的程序集,将进行此替换。

如果您知道编译时的值,常量当然是有用的工具。如果不这样做,但要确保仅将变量设置一次,则可以在C#中使用readonly关键字(映射到MSIL中的initonly)来指示只能在构造函数中设置变量的值;之后,更改它是错误的。当字段有助于确定类的标识时通常使用该字段,并且该字段通常设置为等于构造函数参数。

但是我为什么要体验更好的表现? (甚至考虑到它是非常可交易的)?那内存占用呢?

预先感谢

1 个答案:

答案 0 :(得分:2)

考虑以下代码:

public class Program
{
    public const int ConstField1 = 1;
    public const int ConstField2 = 2;
    public const int ConstField3 = 3;
    public const int ConstField4 = 4;
}

这四个const int32数字仅存储在与程序集元数据相对应的内存中(因此可以通过反射获得),而不存储在实际的运行时类型信息中。与static readonly相比,这节省了16个字节的内存。对于字符串,运行时也不必在实际将其用于其他代码之前就分配该字符串(因为ldstr不用于初始化字段)。您可能会争辩说,这并不会节省太多,但请考虑枚举-它们基本上是带有很多const字段的静态类型。

性能的提升也是显而易见的-由于无需每次使用都获取值,减少了内存碎片,并且可以对其他不可能的值执行其他优化(例如简化像BindingFlags.NonPublic | BindingFlags.Instance这样的表达式。同样,也不需要调用静态构造函数,这是另一点(尽管在某些情况下可能不被调用,请参见beforefieldinit)。