Why Might Using Public Const Not Be a Good Idea?
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.