C# 101
LANGUAGES: C#
ASP.NET VERSIONS: All
Constant
Comprehension
Understanding
C# Constants
By Bill
Wagner
Pop
quiz: What's the difference between these three declarations? And, more
importantly, when should you use each one?
private const
int _Millenium = 2000;
private static
readonly
DateTime _classCreation = DateTime.Now;
private readonly
DateTime _InstanceTime = DateTime.Now;
The
first creates a compile-time constant, the second creates a run-time class
constant, and the third creates a run-time object constant. While developing a
typical program you'll use all three constructs, so it pays to understand the
difference. This article will explain the differences between these three
constructs and show you when to use each.
Compile-time
Constants
Let's
begin with compile-time constants. The symbols you define for compile-time
constants are replaced with the value of the constant at compile time.
Therefore this construct:
if (myDateTime.Year == _Millenium)
compiles
to the same IL as if you had written:
if (myDateTime.Year == 2000)
The
compiler replaces the symbol with the value of the constant. This is the main
point to compile-time constants: These symbols don't exist in the IL, only in
your C# source. Once compiled, you have the same IL as if you had used the
numeric constants in your code.
This
implementation of compile-time constants places other restrictions on declaring
constants and assigning values to them. First, compile-time constants can only
be used effectively for primitive types, enums, or strings. Primitive types are
the built-in integral and floating-point types. These are the only types that
allow you to assign meaningful constant values as part of the initialization
process.
The only
constant 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 constant
value. In other words, the following construct won't compile:
private const
DateTime _classCreation =
new
DateTime(2000, 1, 1, 0, 0, 0);
In
practice, this restricts us to value types and strings. Any other reference
type must be null. User-defined
value types simply won't work at all. For example:
struct MyStruct
{
// ...
}
private static
const MyStruct _s; // Doesn't compile.
So, a
compile-time constant can only be used for primitive types. The IL generated
for a compile-time constant contains the value, not the symbol. The value is
"burned in" at compile time.
readonly
Values
readonly values are also constants, in that
they 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 with
run-time constants. For one thing, run-time constants can be of any type; as
long as you can assign them in your constructors, they will work. I could make readonly values from DateTime
structures; I could not create DateTime values with const.
Secondly,
you can use readonly values for
instance constants, storing different values for each instance of a class type.
As we saw at the start of this article, the value of _instanceTime is
different for every instance of the object being created.
The most
important distinction is that readonly
values are resolved at run time. The IL generated when you reference a readonly constant reference the readonly variable, not the value.
Decisions,
Decisions
The main
difference between const and readonly fields is in their
flexibility. Suppose you've defined both const
and readonly fields in an assembly named
Infrastructure:
public class
UsefulValues
{
public static readonly
UsefulInteger = 5;
public const AnotherUsefulInteger = 10;
}
Then, in
an assembly named Application you reference those values:
for (int
i = UsefulValues.UsefulInteger;
i < UsefulValues.AnotherUsefulInteger;
i++)
Console.WriteLine("value is {0}",
i);
If you
run your little test, you see the following obvious output:
Value is 5
Value is 6
...
Value is 9
Time
passes and you release a new version of the Infrastructure assembly with the
following changes:
public class
UsefulValues
{
public static readonly
UsefulInteger = 105;
public const AnotherUsefulInteger = 120;
}
You
distribute the Infrastructure assembly without rebuilding your Application
assembly. What do you suppose happens?
You'll
get no output at all. The loop now uses the value 105 for its start, and 10 for
its end condition. The const value
of 10 was placed into the Application assembly by the C# compiler. Contrast
that with the UsefulInteger value. It was declared as readonly; it gets resolved at run time. Therefore, the Application
assembly makes use of the new value without even recompiling the Application
assembly. Simply installing an updated version of the Infrastructure assembly
is enough. The point here is that updating the value of a public constant is
really an interface change. Updating the value of a readonly constant is easily upgradeable.
Using readonly constants will also generate a
smaller assembly. Every time you use a const
value, the compiler inserts the value of the constant. When you reference a readonly value, the compiler references
that symbol. Repeatedly storing the actual values in the IL will result in a
larger assembly than repeatedly referencing the same location. This particular
argument does not apply to strings because .NET replaces duplicate strings
using a process called string interning. The result is that const strings generate more or less the
same IL as readonly strings.
Are
there any advantages to using const
over readonly? Yes; constants can be
used in places where readonly values
cannot, namely attributes. You can use const
values as the parameters to attribute constructors; you cannot use readonly values, or variables. So, when
you define objects to use when constructing attributes, those values used for
attribute parameters must be const; readonly doesn't work. Figure 1 shows
an example of a simple attribute to tag classes with their state.
[AttributeUsage
(AttributeTargets.Class)]
public class
ClassStateAttribute : Attribute
{
[Flags]
public enum CodeState
{
Experimental = 0x01,
Stable = 0x02,
Released = 0x04
}
public const CodeState Release2Upgrade =
CodeState.Released |
CodeState.Experimental;
public readonly CodeState TheState;
public
ClassStateAttribute (CodeState s)
{
TheState = s;
}
}
[ClassState
(ClassState.Release2Upgrade)]
public class
NewCode
{
// Etc.
}
Figure
1: A simple
attribute to tag classes with their state.
You
could not, however, rewrite it using readonly
values, as shown in Figure 2.
[AttributeUsage
(AttributeTargets.Class)]
public class
ClassStateAttribute : 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 readonly
CodeState Release2Upgrade =
CodeState.Released |
CodeState.Experimental;
public
ClassStateAttribute(CodeState s)
{
TheState = s;
}
}
[ClassState
(ClassState.Release2Upgrade)]
public class
NewCode
{
// Etc.
}
Figure
2: Readonly values won't work on our
simple example.
The readonly type doesn't work to
initialize an attribute. The actual value of the object must be available at
compile time for the attribute to get created correctly. Therefore, only values
declared as const (or enums) can be
used in this instance.
I always
get questions about the relative performance of const and readonly
values. Frankly, I've never been able to measure any difference between the
two; for any operation I've tried, they are equivalent. The table in Figure 3
summarizes the different use cases I've discussed, and offers my
recommendations.
|
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.
|
Figure
3: Recommendations
for the use cases discussed in this article.
Conclusion
There
are 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 every
assembly that uses a const value. In
the case of readonly you need only
update the definition. This flexibility greatly overrides the minimal
performance gains from using const
as the key. Minimize your use of const
to attribute parameters and enums. Everything else should be declared readonly instead.
Bill Wagner began developing commercial software in
1986. Bill founded SRT Solutions, a firm that specializes in advancing software
development. He began writing magazine articles in 1992. He wrote the C# Core Language Little
Black Book (Paraglyph Publishing, 2001) and is currently writing Effective C# for
Addison-Wesley. Bill is the Microsoft Regional Director for Michigan. Contact
Bill at mailto:wwagner@srtsolutions.com.