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

标签 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 代码(通过 IL 浏览器从 resharp 获取)

我看到以下代码

.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

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

Pushes a new object reference to a string literal stored in the metadata

我已阅读 here那个

Now, wherever myInt is referenced in the code, instead of having to do a "ldloc.0" to get the value from the variable, the MSIL just loads the constant value which is hardcoded into the MSIL. As such, there's usually a small performance and memory advantage to using constants.However, in order to use them you must have the value of the variable at compile time, and any references to this constant at compile time, even if they're in a different assembly, will have this substitution made.

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

但是为什么我应该体验更好的性能呢? (即使考虑到它很容易理解)?内存占用情况如何?

提前致谢

最佳答案

考虑这段代码:

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)。

关于c# - 澄清 IL 生成的引用字符串的代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53767383/

相关文章:

c# - C#中的函数指针数组

c# - 获取所有工作表

c# - 四舍五入

c# - 如何将CIL代码插入到C#中

c# - ldstr 是否在内部实现了 newobj?

c# - 在 Unity Animator 中使用 "Any State"的问题

c# - 动态数据显示 : Change X-Axis date time format for graph

.net - .Net 中的 DAX DynamoDB 缓存?

.net - 有没有一种好方法可以检测 Linq-To-Entities 查询中的空结果?

CIL堆栈交换指令