C# 101
LANGUAGES: C#
ASP.NET VERSIONS: All
ConstantComprehension
UnderstandingC# Constants
By BillWagner
Popquiz: What's the difference between these three declarations? And, moreimportantly, when should you use each one?
private constint _Millenium = 2000;
private staticreadonly
DateTime _classCreation = DateTime.Now;
private readonlyDateTime _InstanceTime = DateTime.Now;
Thefirst creates a compile-time constant, the second creates a run-time classconstant, and the third creates a run-time object constant. While developing atypical program you'll use all three constructs, so it pays to understand thedifference. This article will explain the differences between these threeconstructs and show you when to use each.
Compile-timeConstants
Let'sbegin with compile-time constants. The symbols you define for compile-timeconstants are replaced with the value of the constant at compile time.Therefore this construct:
if (myDateTime.Year == _Millenium)
compilesto the same IL as if you had written:
if (myDateTime.Year == 2000)
Thecompiler replaces the symbol with the value of the constant. This is the mainpoint to compile-time constants: These symbols don't exist in the IL, only inyour C# source. Once compiled, you have the same IL as if you had used thenumeric constants in your code.
Thisimplementation of compile-time constants places other restrictions on declaringconstants and assigning values to them. First, compile-time constants can onlybe used effectively for primitive types, enums, or strings. Primitive types arethe built-in integral and floating-point types. These are the only types thatallow you to assign meaningful constant values as part of the initializationprocess.
The onlyconstant value you can assign to reference types is null. The reason for this restriction is that you cannot use the new operator when you assign a constantvalue. In other words, the following construct won't compile:
private constDateTime _classCreation =
newDateTime(2000, 1, 1, 0, 0, 0);
Inpractice, this restricts us to value types and strings. Any other referencetype must be null. User-definedvalue types simply won't work at all. For example:
struct MyStruct
{
// ...
}
private staticconst MyStruct _s; // Doesn't compile.
So, acompile-time constant can only be used for primitive types. The IL generatedfor a compile-time constant contains the value, not the symbol. The value is"burned in" at compile time.
readonlyValues
readonly values are also constants, in thatthey cannot be modified after the constructor has executed. readonly values are different, however,in that they're set at run time. You have much more flexibility in working withrun-time constants. For one thing, run-time constants can be of any type; aslong as you can assign them in your constructors, they will work. I could make readonly values from DateTimestructures; I could not create DateTime values with const.
Secondly,you can use readonly values forinstance constants, storing different values for each instance of a class type.As we saw at the start of this article, the value of _instanceTime isdifferent for every instance of the object being created.
The mostimportant distinction is that readonlyvalues are resolved at run time. The IL generated when you reference a readonly constant reference the readonly variable, not the value.
Decisions,Decisions
The maindifference between const and readonly fields is in theirflexibility. Suppose you've defined both constand readonly fields in an assembly namedInfrastructure:
public classUsefulValues
{
public static readonlyUsefulInteger = 5;
public const AnotherUsefulInteger = 10;
}
Then, inan assembly named Application you reference those values:
for (inti = UsefulValues.UsefulInteger;
i < UsefulValues.AnotherUsefulInteger;i++)
Console.WriteLine("value is {0}",i);
If yourun your little test, you see the following obvious output:
Value is 5
Value is 6
...
Value is 9
Timepasses and you release a new version of the Infrastructure assembly with thefollowing changes:
public classUsefulValues
{
public static readonlyUsefulInteger = 105;
public const AnotherUsefulInteger = 120;
}
Youdistribute the Infrastructure assembly without rebuilding your Applicationassembly. What do you suppose happens?
You'llget no output at all. The loop now uses the value 105 for its start, and 10 forits end condition. The const valueof 10 was placed into the Application assembly by the C# compiler. Contrastthat with the UsefulInteger value. It was declared as readonly; it gets resolved at run time. Therefore, the Applicationassembly makes use of the new value without even recompiling the Applicationassembly. Simply installing an updated version of the Infrastructure assemblyis enough. The point here is that updating the value of a public constant isreally an interface change. Updating the value of a readonly constant is easily upgradeable.
Using readonly constants will also generate asmaller assembly. Every time you use a constvalue, the compiler inserts the value of the constant. When you reference a readonly value, the compiler referencesthat symbol. Repeatedly storing the actual values in the IL will result in alarger assembly than repeatedly referencing the same location. This particularargument does not apply to strings because .NET replaces duplicate stringsusing a process called string interning. The result is that const strings generate more or less thesame IL as readonly strings.
Arethere any advantages to using constover readonly? Yes; constants can beused in places where readonly valuescannot, namely attributes. You can use constvalues as the parameters to attribute constructors; you cannot use readonly values, or variables. So, whenyou define objects to use when constructing attributes, those values used forattribute parameters must be const; readonly doesn't work. Figure 1 showsan example of a simple attribute to tag classes with their state.
[AttributeUsage(AttributeTargets.Class)]
public classClassStateAttribute : Attribute
{
[Flags]
public enum CodeState
{
Experimental = 0x01,
Stable = 0x02,
Released = 0x04
}
public const CodeState Release2Upgrade =
CodeState.Released |CodeState.Experimental;
public readonly CodeState TheState;
publicClassStateAttribute (CodeState s)
{
TheState = s;
}
}
[ClassState(ClassState.Release2Upgrade)]
public classNewCode
{
// Etc.
}
Figure1: A simpleattribute to tag classes with their state.
Youcould not, however, rewrite it using readonlyvalues, as shown in Figure 2.
[AttributeUsage(AttributeTargets.Class)]
public classClassStateAttribute : Attribute
{
[Flags]
public enum CodeState
{
Experimental = 0x01,
Stable = 0x02,
Released = 0x04
}
// Won't work: Only constant values can be used.
// Not read only.
public static readonlyCodeState Release2Upgrade =
CodeState.Released |CodeState.Experimental;
publicClassStateAttribute(CodeState s)
{
TheState = s;
}
}
[ClassState(ClassState.Release2Upgrade)]
public classNewCode
{
// Etc.
}
Figure2: Readonly values won't work on oursimple example.
The readonly type doesn't work toinitialize an attribute. The actual value of the object must be available atcompile time for the attribute to get created correctly. Therefore, only valuesdeclared as const (or enums) can beused in this instance.
I alwaysget questions about the relative performance of const and readonlyvalues. Frankly, I've never been able to measure any difference between thetwo; for any operation I've tried, they are equivalent. The table in Figure 3summarizes the different use cases I've discussed, and offers myrecommendations.
| Usage | readonly | const | Comments and Recommendations |
| Primitive constant | Yes | Yes | Use const. Primitive types that will never change should be const. |
| Release Dependent const, primitive type | Yes | Yes | Use static readonly. Any constant value that might change should be readonly, not const. |
| Other constants | Yes | No | Use static readonly. It's the only one that works. |
| Immutable members | Yes | No | Use (instance) readonly. It's the only option, and immutability is enforced by the compiler. |
| Enumerated values | No | Yes | Enumerated values must be const. |
| Values used to construct attributes | No | Yes | These must be constants. |
Figure3: Recommendationsfor the use cases discussed in this article.
Conclusion
Thereare some small performance gains to be realized from using const instead of readonly,but you give up quite a bit of flexibility. You'll need to recompile everyassembly that uses a const value. Inthe case of readonly you need onlyupdate the definition. This flexibility greatly overrides the minimalperformance gains from using constas the key. Minimize your use of constto attribute parameters and enums. Everything else should be declared readonly instead.
Bill Wagner began developing commercial software in1986. Bill founded SRT Solutions, a firm that specializes in advancing softwaredevelopment. He began writing magazine articles in 1992. He wrote the C# Core Language LittleBlack Book (Paraglyph Publishing, 2001) and is currently writing Effective C# forAddison-Wesley. Bill is the Microsoft Regional Director for Michigan. ContactBill at mailto:wwagner@srtsolutions.com.