const string vs. static string
Sunday, May 20th, 2007There was an email thread going around the office not too long ago debating whether it is more efficient to use string constants or static readonly strings (in C#). An example was given that seemed to indicate that the static strings were better:
public class Sample
{
static readonly string StaticString = "StaticString";
const string ConstString = "ConstString";
public void SetValues(StringCollection strings)
{
strings[StaticString] = "foo";
strings[ConstString] = "bar";
}
public void GetValues(StringCollection strings)
{
string value1 = strings[StaticString];
string value2 = strings[ConstString];
}
}
The disassembly of that code shows something surprising:
public class Sample
{
// Fields
private const string ConstString = "ConstString";
private static readonly string StaticString = "StaticString";
// Methods
public void GetValues(NameValueCollection values)
{
string text1 = values[StaticString];
string text2 = values["ConstString"];
}
public void SetValues(NameValueCollection values)
{
values[StaticString] = "foo";
values["ConstString"] = "bar";
}
}
It doesn't look like the const string is being used at all, while it's obvious that the static string is. This can't be right. Let's take a look at the IL:
.class public auto ansi beforefieldinit Sample
extends [mscorlib]System.Object
{
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
.maxstack 8
L_0000: ldstr "StaticString"
L_0005: stsfld string TestStringConstantsLibrary.Sample::StaticString
L_000a: ret
}
.method public hidebysig specialname rtspecialname instance
void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.method public hidebysig instance void GetValues(class
[System]System.Collections.Specialized.NameValueCollection values) cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: ldsfld string TestStringConstantsLibrary.Sample::StaticString
L_0006: callvirt instance string [System]System.Collections.Specialized.NameValueCollection::get_Item(string)
L_000b: pop
L_000c: ldarg.1
L_000d: ldstr "ConstString"
L_0012: callvirt instance string [System]System.Collections.Specialized.NameValueCollection::get_Item(string)
L_0017: pop
L_0018: ret
}
.method public hidebysig instance void SetValues(class
[System]System.Collections.Specialized.NameValueCollection values) cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: ldsfld string TestStringConstantsLibrary.Sample::StaticString
L_0006: ldstr "foo"
L_000b: callvirt instance void [System]System.Collections.Specialized.NameValueCollection::set_Item(string, string)
L_0010: ldarg.1
L_0011: ldstr "ConstString"
L_0016: ldstr "bar"
L_001b: callvirt instance void [System]System.Collections.Specialized.NameValueCollection::set_Item(string, string)
L_0020: ret
}
.field private static literal string ConstString = string('ConstString')
.field private static initonly string StaticString
}
Same odd result, though we can see that the const string is defined as a static field (with the literal modifier instead of the initonly modifier). What gives? I refuse to believe that const strings are duplicated all over the assembly. Opening the assembly in a hex editor (I used Visual Studio's Binary Editor), you can see that there are only 2 instances of the string (not the 3 you would expect):
In fact, if we add the following method to our class:
public void WriteConsole()
{
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
Console.WriteLine("ConstString");
}
We can see that it does not add any additional strings to our assembly. There are still only the 2 instances:
So, const string is not as bad as it initially looks. I am curious why there are 2 instances of the string though, and why the field isn’t referenced…