Is declaring a number or string as public const
considered bad practice? Let's have a look what a const
variable means in the first place.
Let's take the definition from the official C# Reference:
You use the
const
keyword to declare a constant field or a constant local. Constant fields and locals aren't variables and may not be modified. Constants can be numbers, Boolean values, strings, or a null reference. Don’t create a constant to represent information that you expect to change at any time.
Now for startes this part is important "Constant fields and locals aren't variables...". That is essential. Let's have a look at the following code snippet:
using System;
Console.Write(MyClass.MyConstantString);
public class MyClass
{
public const string MyConstantString = "Hello";
}
The compiler will lower that term to the following code Source on sharplab.io
internal class Program
{
private static void <Main>$(string[] args)
{
Console.Write("Hello");
}
}
public class MyClass
{
public const string MyConstantString = "Hello";
}
Hupps. Where is the reference to MyClass.MyConstantString
in the Console.Write
? Exactly not existing anymore. const
fields and locals are indeed no variables and therefore will be "replaced" by the compiler at compile-time everywhere the constant is used.
That can bring a big problem:
Referencing assemblies
Just imagine you have the following class in Assembly A:
public class MyClassInAssemblyA
{
public const string MyConstantString = "Hello";
}
And an user of the class in Assembly B:
public class MyClassInAssemblyA
{
public void WriteSomething() => Console.WriteLine(MyClassInAssemblyA.MyConstantString);
}
If we compile this and call WriteSomething
the console will print "Hello". Now we go further and change MyConstantString
from "Hello" to "World". We redeploy Assembly A but not Assembly B, what will be the output of WriteSomething
? Still "Hello". Why? Because as shown earlier the const
string gets hardcoded into the assembly. That means to reflect the latest changes you also have to recompile Assembly B. Now that is not a big use when you develop software where everything is under your control, but it can get tricky for example if you develop plugins, where the caller does not automatically get recompiled.
How to fix that
The easiest fix is to use a readonly static
field. So taken our example from the beginning, we refactor this to a readonly static
field:
using System;
Console.Write(MyClass.MyConstantString);
public class MyClass
{
public static readonly string MyConstantString = "Hello";
}
And the resulting code will look like this:
internal class Program
{
private static void <Main>$(string[] args)
{
Console.Write(MyClass.MyConstantString);
}
}
public class MyClass
{
public static readonly string MyConstantString = "Hello";
}
We can clearly see that the Console.Write
now takes the "real" reference rather than the hardcoded string. Meaning if we change MyClass.MyConstantString
it gets automatically reflected there as well.
⚠️ Changing from public const
to public static readonly
is considered a breaking change. You lose binary, source, and reflection compatibility. So if you do semantic versioning you'd have to increase the major number. Be aware of that.
Also you can't use static readonly
fields/variables in the following scenarios:
- Attribute arguments
- Switch statements
- Optional parameter declarations
Is public const
bad then?
As always: It depends. Mainly on the fact if you have full control over the whole flow and source code, if you have most likely not. Also if you are 100% certain that your const
will never change, go for it. Something like public const int Zero = 0
or Pi. Otherwise be very cautious if you want to use public const
. The same applies for protected const
. Everything which is visible outside your assembly.
Of course private const
or internal const
are safe in this regard.