C# 对引用字符串的IL生成代码的澄清
我今天正在进行一些重构,我注意到一件奇怪的事情,我不明白……或者更好的是,我部分同意我在网上发现的东西,但仍然有一些问题C# 对引用字符串的IL生成代码的澄清,c#,.net,cil,C#,.net,Cil,我今天正在进行一些重构,我注意到一件奇怪的事情,我不明白……或者更好的是,我部分同意我在网上发现的东西,但仍然有一些问题 请考虑这个简单的例子 class Program { public static readonly string a = "a"; public const string b = "b"; static void Main(string[] args) { Console.WriteLine(a); Con
请考虑这个简单的例子
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
对于静态字符串,它的行为与我预期的一样。
相反,对于常量,它在堆栈上加载一个新值。。。事实上,看看它说的ldstr操作码 将新对象引用推送到元数据中存储的字符串文字 我读过 现在,只要代码中引用myInt,MSIL就只加载硬编码到MSIL中的常量值,而不必执行“ldloc.0”从变量中获取值。因此,使用常量通常会带来较小的性能和内存优势。但是,为了使用常量,您必须在编译时拥有变量的值,并且在编译时对该常量的任何引用,即使它们位于不同的程序集中,都将进行此替换 如果您在编译时知道该值,常数当然是一个有用的工具。如果不需要,但希望确保变量只设置一次,可以使用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数字只存储在与程序集元数据相对应的内存中(因此可以通过反射获得),而不存储在实际的运行时类型信息中。与静态只读相比,这节省了16字节的内存。对于字符串,在其他代码中实际使用字符串之前,运行时也不必分配字符串(因为ldstr
不用于初始化字段)。你可能会认为这并不能节省很多,但是考虑枚举——它们基本上是静态类型,有很多const字段。
性能的提高也是显而易见的——因为不需要每次使用该值时都获取它,内存碎片会减少,并且可以对该值执行其他用户无法执行的其他优化(例如简化表达式,如
bindingsflags.NonPublic | bindingsflags.Instance
)。另外,不需要调用静态构造函数,因此这是另一点(尽管在某些情况下可能不会调用它,请参见beforefieldinit
)。“相反,对于常量,它会在堆栈上加载一个新值”-在这两种情况下,它不是都会在堆栈上加载引用吗?我不清楚担心的是什么。(如果你担心的话,它不是每次都创建一个新的字符串对象。)“静态字符串”没有帮助a
是存储对字符串对象的引用的变量<代码>b不是变量,而是常量。编译器只是在代码中使用常量标识符的地方替换文本ldstr
经过了大量优化,因为它非常常见,抖动会生成一条MOV reg、imm指令ldsfld
也不是无精打采的,但需要内存访问MOV reg[addr]。这并不重要,因为实际使用字符串通常需要更长的时间。当您使用Console.WriteLine()时,时间要长得多。谢谢您的回复,因为@HansPassant我对Console.WriteLine()的使用只是一个示例。。。