There 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…