Const Strings – a very convenient way to shoot yourself in the foot

If you want to have a static readonly string, you have two options:

public static class MyStringTestClass
{
    public static readonly string StaticReadonly = "Static ReadOnly String";
    public const string ConstString = "Const String";
}

The difference is subtle at first: StaticReadonly is like a normal field that gets initialized through a static constructor, while ConstString is "hardcoded". If you look the Assembly at it in Reflector, it looks like this:

public static class MyStringTestClass
{
    // Fields
    public const string ConstString = "Const String";
    public static readonly string StaticReadonly;

    // Methods
    static MyStringTestClass()
    {
        StaticReadonly = "Static ReadOnly String";
    }
}

So the obvious difference is that a static readonly string is initialized in the constructor (which also means that you can set it dynamically a construction), while a const string is really carved in stone. But the real difference is more subtle. Let's create a second assembly, reference the first one and add a class:

    public class MyStringTestConsumer
    {
        public void TestMethod()
        {
            string sro = MyStringTestClass.StaticReadonly;
            SomeOtherFunction(sro);
            string sc = MyStringTestClass.ConstString;
            SomeOtherFunction(sc);
        }

        public void SomeOtherFunction(string input)
        {
            // Dummy function to prevent "string sc"
            // being optimized away by the compiler
        }
    }

Compile this second assembly, load it into reflector, and look at TestMethod:

public void TestMethod()
{
    string sro = MyStringTestClass.StaticReadonly;
    this.SomeOtherFunction(sro);
    string sc = "Const String";
    this.SomeOtherFunction(sc);
}

As you see, A const string is not a reference to something. While a static readonly string is still a reference to a field in a class which will be resolved only at runtime, a const string is actually "copy/pasted" by the compiler. What does that mean? At first, it means a theoretical performance increase, because no lookup will take place, hence it is usually recommended by FxCop.

But there is one big caveat with it: Say you have multiple assemblies, one that provides the const and one that consumes it. What happens if you change the two string in the "provider.dll" without touching the consumer.dll? The MyStringTestClass.StaticReadonly will point to the new reference, but the const string will not change, because it had been literally inserted. You will need to recompile the consumer.dll to have the string replaced with the new one.

I have just been bitten by this. I have two provider.dlls, one for a test and one for a live environment, but only one consumer.dll. I accidentally declared a string as const. Needless to say, deploying the consumer.dll to a live environment led to some... interesting... results. So yeah: Consts are really useful, but sometimes a static readonly field works better.

PS: I think the same applies to other value-types like int as well, but I never shot myself in the foot with an int.

Comments (6)

Andrei RineaOctober 3rd, 2009 at 23:03

I've been bitten by this too but in a different form : I was relying on the first access to any of my class members to trigger the static constructor. And it worked excellent except for the constants. So watch out for this too!

StephenDecember 6th, 2010 at 14:51

Very good tip, This is definitely something worth remembering. It's also interesting to see that the JIT Compiler moves the static readonly assignment to the constructor.

PunitOctober 10th, 2012 at 17:20

Great Explanation and insight. Thanks for taking the time to write this.

Rubens MariuzzoJanuary 31st, 2013 at 02:51

Very well explained, thanks!

AakashMay 28th, 2013 at 09:45

Thanks for putting this forward with a brilliant explanation

[…] Incidentally, if you’re wondering why the string is static readonly rather than const then here’s why. […]