Why I don’t like public constants and enum in C#
As it said in the Bible of clean code “Code Complete: A Practical Handbook of Software Construction” by Steve McConnell, that we have to avoid magic strings (and any other magic) in our code. And many programmers understand this precept such a way that we have to create constants for every unchanging value.
Let’s demolish a software application written like this.
We have two .NET Core projects (or .NET Framework, it doesn’t matter).
- Class library ConstClass:
public class ConstClass{ public const string ConstValue = "This is a value";}
2. Console application:
class Program{ static void Main(string[] args) { Console.WriteLine(ConstClass.ConstValue); Console.ReadKey(); }}
In Console application we added dll of our class as a reference.
After running console application we are getting expected output:
We successfully deployed it to the production environment and forgot about it. But after long period time the paradigm changed and we reassigned our ConstValue:
public class ConstClass{ public const string ConstValue = "This is another value";}
We built the class library and replaced old one on the server with new one. Launching the console application, but still getting
Why??? We changed the constant string and redeployed the dll.
Let’s investigate our console application under the hood.
After ildasm launch on console application we see:
IL_0001: ldstr "This is a value"IL_0006: call void [System.Console]System.Console::WriteLine(string)IL_000b: nopIL_000c: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()
So the value of constant assigned during compilation, not in runtime. Technically our console application doesn’t depends on class library in this case anymore. If we want to apply constant change, we should rebuild console application and redeploy it.
As for me, I don’t use public constants in my code.
Enumeration:
Lets repeat the trick with enum, but we will change programs, because it’s boring to do the same twice.
- We have class library with enumeration and static helper class:
public enum Numbers{ One, Two, Three}public static class Helper{ public static bool CheckIsThree(Numbers num) { return num == Numbers.Three; }}
So in helper class we just check if parameter is Three from enumeration above.
2. Console application:
static void Main(string[] args){ var num = Numbers.Three; Console.WriteLine(Helper.CheckIsThree(num)); Console.ReadKey();}
Build everything, deploy to prod and launch. The obvious result in the output:
Waiting an a year (in our case I waited not so long) to forget the implementation and adding to the enumeration one value to the beginning:
public enum Numbers{ Zero, One, Two, Three}
Build the class library, deploy it to the prod and launch the console application:
Why? Only one question sign, because we already know the problem.
Ildasm our console:
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call bool [TestArticleClass]TestArticleClass.Helper::CheckIsThree(valuetype [TestArticleClass]TestArticleClass.Numbers)
The most interesting string for us is IL_0001. Here we push 2 onto the stack as int32. So IL used the int rank of our enumeration item, but when we added new item to the enumeration, order changed.
So, I don’t use public enumeration too…
Solution:
If I need global public constants shared through several assemblies I define static classes with static readonly strings (or integers, etc.)
public static class Const{ public static class Branch
{
public static readonly string Value1 = "value";
public static readonly int Value2 = 0;
}
}