Search code examples
c#.netcil

Clarification of IL generated code referenced to strings


I was performing some refactoring today and I've noticed a strange thing I can't understand...or better partially I agree with what I've found on the net but still have some questions.

Please consider this simple example

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

        Console.WriteLine(a);

        Console.WriteLine(b);
    }
}

Now if I look at the generated IL Code (got via IL Browser from resharp)

I see the following code

.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

For what concern the static string it behaves as I expected. Instead for the const it loads a new value on the stack... infact looking at the ldstr opcode here it says

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

I've read here that

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.

Constants are certainly a useful tool if you know the value at compile time. If you don't, but want to ensure that your variable is set only once, you can use the readonly keyword in C# (which maps to initonly in MSIL) to indicate that the value of the variable can only be set in the constructor; after that, it's an error to change it. This is often used when a field helps to determine the identity of a class, and is often set equal to a constructor parameter.

But why should I experience a better performance? (even considering it's quite trascurable) ? What about memory footprint?

Thanks in advance


Solution

  • Consider this code:

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

    The four const int32 numbers are only stored in the memory that corresponds to the assembly metadata (so they can be obtained via reflection), but not in the actual run-time type information. In comparison with static readonly, this saves 16 bytes of memory. In the case of strings, the runtime also doesn't have to allocate the string before it is actually used in other code (because ldstr is not used to initialize the field). You might argue that this doesn't save much, but consider enums - they are basically static types with lots of const fields.

    The performance improvement is also apparent - since there is no need to fetch the value every time it is used, memory fragmentation is decreased, and other optimizations could be performed on the value that wouldn't be possible otheriwse (such as simplifying expressions like BindingFlags.NonPublic | BindingFlags.Instance). Also there is no need for the static constructor to be called, so that is another point (although it might not be called in some cases, see beforefieldinit).