我今天正在执行一些重构,我注意到我不明白的一件奇怪的事……或者更好的是,我同意我在网上找到的内容,但仍然有一些疑问。
请考虑以下简单示例
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)来指示只能在构造函数中设置变量的值;之后,更改它是错误的。当字段有助于确定类的标识时通常使用该字段,并且该字段通常设置为等于构造函数参数。
但是我为什么要体验更好的表现? (甚至考虑到它是非常可交易的)?那内存占用呢?
预先感谢
答案 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
)。