How constant is a constant?

How constant are constants?

I got a rude surprise today when I discovered that constants in C# (.NET programs in general) are a little more constant than I thought. I know that sounds strange, but let me explain.

Here’s a C# class that defines two constants and a method that outputs those constants.

using System;

namespace OtherTest
{
    public static class TestObj
    {
        public const int ConstInt = 42;
        public const string ConstString = "First test";

        public static void ShowConstants()
        {
            Console.WriteLine("TestObj.ShowConstants");
            Console.WriteLine("ConstInt = {0}", ConstInt);
            Console.WriteLine("ConstString = {0}", ConstString);
        }
    }
}

We can compile that into OtherTest.dll with a simple command line:

csc /t:library OtherTest.cs

If you’re having trouble running the compiler, go to your All Programs menu and select Visual Studio|Visual Studio Tools|Visual Studio Command Prompt. Then you should be able to run the command line compiler.

Okay so far. Here’s some code that references the constants in that class:

using System;

using OtherTest;

namespace testo
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Inside Program.Main");
            Console.WriteLine("ConstInt = {0}", TestObj.ConstInt);
            Console.WriteLine("ConstString = {0}", TestObj.ConstString);
            TestObj.ShowConstants();
        }
    }
}

Compile that program and link with the OtherTest assembly with this command line:

csc /t:exe /reference:OtherTest.dll ConstTest.cs

That creates ConstTest.exe which, when run, provides this output:

C:\DevWork\ConstTest>ConstTest
Inside Program.Main
ConstInt = 42
ConstString = Firsttest
TestObj.ShowConstants
ConstInt = 42
ConstString = Firsttest

That’s exactly what we expect. Now, change the values of the constants in OtherTest.cs:

        public const int ConstInt = 99604;
        public const string ConstString = "Second test";

And re-compile the assembly:

csc /t:library OtherTest.cs

And run the ConstTest.exe program again:

C:\DevWork\ConstTest>ConstTest.exe
Inside Program.Main
ConstInt = 42
ConstString = Firsttest
TestObj.ShowConstants
ConstInt = 99604
ConstString = Second test

If you’re as surprised as I am, raise your hand. The constant that the main program sees is different from the constant that is defined in the external assembly.

I understand what’s going on, but I’m pretty surprised by it. The compiler, when it compiled ConstTest.cs, reached into OtherTest.cs, got the values of the constants, and included them as constants in the compiled code. So when the code references TestObj.ConstInt at runtime, it’s really just getting the constant 42. That all makes sense. Except, of course, when you change the constant in the OtherTest.cs assembly, re-compile, and the values don’t match.

What surprises me is that the C# compiler (CSC) hoists the constants from the assembly. After all, the compiler is just creating MSIL (intermediate code) that is later compiled to native code by the JIT compiler at runtime. I would have expected CSC to write MSIL that says, in effect, “Use the constant value defined in that other assembly here,” and let the JIT compiler figure out how to optimize everything. That would eliminate the confusion you see above, and I don’t understand why it’s not done that way.

The thing to remember here is that constants are evaluated at compile time. I don’t know all the ramifications of this, but one thing is for certain: if your program references constants that are defined in an external assembly, then your program is at risk any time that assembly is changed. Unless you make the rule that a publicly visible constant never changes value throughout the life of the source code, you’re running the risk of encountering a very difficult to find bug that will “go away” when you re-compile the project. And there’s nothing worse than bugs that just “go away.”

4 comments to How constant is a constant?

  • Jim: Section 10.5.2.2 of The C# Programming Language has the skinny on this. “Constants and readonly fields have different binary versioning semantics. When an expression references a constant, the value of the constant is obtained at compile time [as you discovered], but when an expression references a readonly field, the value of the field in not obtained until runtime.” In the Fourth Edition, Jon Skeet points out you should use constant for true constants (like the number of milliseconds in a second or the minimum value of an int) and readonly for everything that isn’t a constant in the physical sense (and so that might be changed in the manner you show in the future).

    Cheers, Julian

  • Jim

    Julian: Thanks for the reply. I asked the question on StackOverflow and got pretty much the same response from Eric Lippert: “Constants are supposed to be constant. For all time. That’s a much stricter interpretation of “constant” than I’m used to, and in the future I’ll be using readonly fields for things that could change.

  • Jim, nice job dragging this into the light and pointing it out. It’s very counter-intuitive, and it changes my concept of constants as immutable bit arrays into one more deeply rooted in the semantics of the grammar. In the new world they are apparently so universally absolute that the compiler is free to promote them to literals and ignore any future definitions from the declaring libraries. Good to know, thank you Julian.

  • Jim

    Matthew: You’re right, it completely changes one’s view on the meaning of “constant.” It’s a complete departure from how programmers have treated constants–certainly how I’ve learned to treat constants. I’ll be writing more about this.