Beware of const members
I happened to learn a new thing about const today and how one needs to be careful with its usage.
Let’s say I have a third-party assembly ‘ConstVsReadonlyLib’ with a class named ConstSideEffect.cs:
1: public class ConstSideEffect
2: {
3: public static readonly int StartValue = 10;
4: public const int EndValue = 20;
5: }
In my project, I reference the above assembly as follows:
1: static void Main(string[] args)
2: {
3: for (int i = ConstSideEffect.StartValue; i < ConstSideEffect.EndValue; i++)
4: {
5: Console.WriteLine(i);
6: }
7: Console.ReadLine();
8: }
You’ll see values 10 through 19 as expected. Now, let’s say I receive a new version of the ConstVsReadonlyLib.
1: public class ConstSideEffect
2: {
3: public static readonly int StartValue = 5;
4: public const int EndValue = 30;
5: }
If I just drop this new assembly in the bin folder and run the application, without rebuilding my console application, my thinking was that the output would be from 5 to 29. Of course I was wrong… if not you’d not be reading this blog.
The actual output is from 5 through 19. The reason is due to the behavior of const and readonly members.
To begin with, const is the compile-time constant and readonly is a runtime constant. Next, when you compile the code, a compile-time constant member is replaced with the value of the constant in the code. But, the IL generated when you reference a read-only constant, references the readonly variable, not its value.
So, the IL version of the Main method, after compilation actually looks something like:
1: static void Main(string[] args)
2: {
3: for (int i = ConstSideEffect.StartValue; i < 20; i++)
4: {
5: Console.WriteLine(i);
6: }
7: Console.ReadLine();
8: }
I’m no expert with this IL thingi, but when I look at the disassembled code of the exe file (using IL Disassembler), I see the following:
I see our readonly member still being referenced by the variable name (ConstVsReadonlyLib.ConstSideEffect::StartValue) in line 0001. Then there’s the Console.WriteLine in line 000b and finally, see the value of 20 in line 0017. This, I’m pretty sure is our const member being replaced by its value which marks the upper bound of the ‘for’ loop. Now you know why the output was from 5 through 19.
This definitely is a side-effect of having const members and one needs to be aware of it.
While we’re here, I’d like to add a few other points about const and readonly members:
- const is slightly faster, but is less flexible
- readonly cannot be declared within a method scope
- const can be used only on primitive types (numbers and strings)
Just wanted to share this before going to bed!