Why Might Using Public Const Not Be a Good Idea?

November 28th 2010 C# MSIL

While reading an article on the difference between const and readonly it surprised me that changes to public consts in the referenced assembly don't affect the referencing assembly unless it is recompiled using the changed referenced assembly. The C# Reference doesn't hint at such behavior at all, which means it's time for further exploration.

A sample can be pretty straight forward. Let's start with a single class in the library:

public class Urls
{
    public const string ProductWebPage = "http://www.damirscorner.com/myproduct";
}

Then reference the library in a test application:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Product URL: " + Urls.ProductWebPage);
        Console.ReadKey();
    }
}

Compiling both assemblies and running the application returns the expected result. The surprise comes if the value in the library changes and the application doesn't get recompiled. The new value in the library could be:

public class Urls
{
    public const string ProductWebPage = "http://www.myproduct.com";
}

The application will return the new value only if it is also recompiled. As long as only the changed referenced assembly is copied to the application folder, the old URL value will still be displayed. Checking the compiled MSIL, this behavior can easily be explained:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       19 (0x13)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Product URL: http://www.myproduct.com"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  call       valuetype [mscorlib]System.ConsoleKeyInfo 
                                 [mscorlib]System.Console::ReadKey()
  IL_0011:  pop
  IL_0012:  ret
} // end of method Program::Main

The constant value is not referenced at all. It is included into the referencing assembly as a literal. Consequently the compiled application doesn't reference the library in its manifest and will still work even if the library is deleted from the application folder:

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly MyProduct
{
  // ...

To avoid the problem, public constant values in libraries should always use static readonly modifiers instead of const:

public class Urls
{
    public static readonly string ProductWebPage = 
        "http://www.damirscorner.com/myproduct";
}

The application code remains the same; the compiled MSIL is different:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       29 (0x1d)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Product URL: "
  IL_0006:  ldsfld     string [Library]DamirsCorner.Samples.Const.Urls::ProductWebPage
  IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0015:  nop
  IL_0016:  call       valuetype [mscorlib]System.ConsoleKeyInfo 
                                 [mscorlib]System.Console::ReadKey()
  IL_001b:  pop
  IL_001c:  ret
} // end of method Program::Main

Now the changes in the library will reflect in the application even without recompiling it. Of course the application also references the library in its manifest and won't work without it:

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly extern Library
{
  .ver 1:0:0:0
}
.assembly MyProduct
{
  // ...

The moral of the story: "Know Thy Language". And never use const for public constant values in libraries.

Copyright
Creative Commons License