Once again we delve into the world of C# Fundamentals. Those hints and gotchas that tend to bite folks newer to the language. Today I take another look about the differences between const and readonly and the uses for each. For the purposes of this entry, whenever I say constant i mean a const or readonly.
Const – Compile-Time Constant Values
First of all, for those of you who came from the C++ and C# world, const in C# is not the same as const in that language. A const in C# can be closest considered like a #define of a literal value (that is, a value, not a code macro). Const in C# let’s you define constant values. In C#, you can declare a const of any type as long as the value assigned can be fully evaluated at compile time. For the most part, this limits you to numeric types, characters, and strings:
1 : // all legal
2 : public const double Pi = 3.1415927;
3 : public const int DaysInWeek = 7;
4 : public const char SelectedFlag = ‘X’;
5 : public const string ColumnHeading = “Result”;
Now, these are all obvious examples, you can also use expressions as the target value of a const field, but they must be able to be resolved at compile time (i.e. it must use only literals and other consts):
1 : // all legal, these are all resolved at compile time, so there is no
// penalty
2 : // for the math at runtime.
3 : public const int MillisecondsPerSecond = 1000;
4 : public const int MillisecondsPerMinute = 60 * MillisecondsPerSecond;
5 : public const int MillisecondsPerHour = 60 * MillisecondsPerMinute;
6 : 7 : // legal, string concats on literals and consts resolved at compile
// time as well
8 : public const string Title = “Ms.”;
9 : public const string NameFormat = Title + “ { 0 } {
1
}
”;
10 : 11 : // however, you cannot have a const expression with a non-const even
// if it’s static-readonly:
12 : public const string Nothing = string.Empty;
Notice that up above, you can concatenate strings or do math on numbers to make new constant literals. This works because these expressions can be determined at compile time. However, notice that string.Empty is not a const field, it is a static readonly instance, thus it will not allow this to be assigned to a const because it’s not a literal expression.
Now, remember how I said ANY type as long as the value can be evaluated at compile-time. This means that const structs, are impossible because a struct must always call its constructor first to initialize it. Therefore you cannot have a const struct of any value.
You would think this would bar you from having a const class (reference-type) too, but not quite! What are the two types of reference values that can be determined at compile time? We’ve already seen that string can be assigned a value as long as it is literal, const, or a concatenation of literals and consts. The other possibility is null. Yes, any other reference type can be declared const as long as the value you assign to it is null – which turns out to be fairly useless.
So you can’t do this:
1 : // can’t be determined at compile time, all constructor calls are runtime.
2 : public const DbCommand EmptyCommand = new SqlCommand();
But you could do this:
1 : // you can have a const of any reference type as null, but that is limited
// in usefulness.
2 : public const DbCommand EmptyCommand = null;
So, to reiterate, a const can be any type and value combination that can be determined at compile time. This is much like the #define pre-compiler facility in C and C++. Because these are resolved at compile time, they can also be substituted in the code at compile-time. And indeed this is what occurs.
Now if the scope of your constant is limited to just one assembly, class, or smaller (you can define a const inside a method), this is not a big deal. However, if the const is visible outside the assembly it is defined in, we must be wary! Because the const’s value is substituted at compile time, this means that if the assembly that defines the const changes its value, but the calling assembly isn’t recompiled, then the calling assembly will still see the original value.
Let’s illustrate. Assume a class library called Shapes.DLL that defines this:
1 : public class Circle
2 : {
3 : public const double Pi = 3.14;
4 : 5 : // ...
6:
}
Now let’s assume a separate program called Drawing.EXE adds a reference to Shapes.DLL and uses the const:
1 : public static class Drawing
2 : {
3 : public static void Main() 4 : {
5 : Console.WriteLine(“Pi is: “ + Circle.Pi);
6:
}
7:
}
If we run this, we get:
1 : Pi is 3.14
Now let’s say during the QA process someone decides that this value of Pi is not precise enough and changes the definition:
1: public const double Pi = 3.1415927;
And they rebuild the Shapes.DLL and just drop it into the deployment directory for Drawing.EXE without rebuilding it. What happens if we run Drawing.EXE again without recompiling? We get:
1 : Pi is 3.14
Whoa! Even though we changed the value of Pi in our referenced assembly and deployed it to where Drawing.EXE expected it and Drawing.EXE loaded it, it still prints 3.14. This is because const is a compile-time substitution. Thus, if you change the value of a const, it will not be picked up until the code using it is recompiled as well. If we recompile and run Drawing.EXE, we will get:
1 : Pi is 3.1415927
Thus, const should be used mainly for values that are not subject to change, or freely if the scope of the const is limited to the same assembly or smaller.
Readonly – Runtime Constant References
So we’ve seen that const allows us to define compile-time constant values. But what if want to define a constant that will be changed without having to recompile? Well, using the readonly keyword in C#, you can tell the compiler that the variable can only be assigned a value at initialization or in a constructor. Now, readonly doesn’t have a keyword equivalent in C++, but you could consider it to be analogous to a const pointer (like SomeType * const) where the pointer can’t be changed, but the contents of the pointer can. This is also identical to the behavior of the final keyword in Java.
Since readonly fields are not limited to values that can be determined at compile-time, this means that they are not substituted where they are used at compile-time, but are the same as other variable lookups. This means that you won’t have the situation like with const where a class library can change a const and another assembly using it won’t get the updated value.
So if we have in a Payroll.DLL assembly:
1 : public class HourlyPay
2 : {
3 : public static readonly int WorkDaysPerWeek = 7;
4 : 5 : // ...
6:
}
And we use it in a class in a separate assembly called HR.EXE:
1 : public static class HumanResourcesApp
2 : {
3 : public static void Main() 4 : {
5 : Console.WriteLine("The number of workdays per week is: " +
HourlyPay.WorkDaysPerWeek);
6:
}
7:
}
If we run this, we get the expected output:
1 : The number of workdays per week is 7
But if we change the value in Payroll.DLL to 5 instead and re-run, we will get the right output even if we don’t recompile HR.EXE. This is because as we said before readonly fields are resolved at run-time like any other variables. This gives them a level of safety higher than const for constants that are subject to change over time. Note that WorkDaysPerWeek may be const now given the company’s current policy, so it’s feasible to make it readonly. But because it may change a year from now, it’s less desirable as a const if it’s visible from other assemblies.
So, things that are truly const (hours per day, days per week) can be safely made const because they never, ever change. But things that may be constant now but could conceivably change in the future due to changes in requirement or policy should be made readonly instead, especially if they are visible from other assemblies.
Because readonly fields are looked up at run-time, they are also slightly less efficient than compile-time const. Now, I always believe you should choose safety over performance — especially here since the performance gain is negligible. I only mention it here because if you run static code analysis in VisualStudio (or using FxCop directly) it will mention the performance difference.
Another nice thing about readonly is that, since it’s resolved at run-time, you can make struct and class instances readonly as well:
1 : public struct Point
2 : {
3 : public int X { get; set; }
4 : public int Y { get; set; }
5:
}
6 : 7 : public static class Canvas
8 : {
9 : public static readonly Point Origin = new Point { X = 0, Y = 0 };
10 : 11 : public static void Main() 12 : {
13 : // can't do these, Origin and it's fields can't be changed:
14 : Origin = new Point(); // compiler error!
15 : Origin.X = 7; // also compiler error!
16:
}
17:
}
Since Origin is a struct type, we can’t use const. However we can use readonly. Now when Origin is used (or sometime before depending on the JIT optimizations) it will initialize and can’t be changed.
Now, something curious: if your readonly field is a value type (primitive or struct) or is immutable (type that can’t be changed once assigned like string), that field will behave like a constant. However, if you have a reference type what happens? Let’s say that we decided to make Point a reference type (class) instead:
1 : public class Point
2 : {
3 : public int X { get; set; }
4 : public int Y { get; set; }
5:
}
6 : 7 : public static class Canvas
8 : {
9 : public static readonly Point Origin = new Point { X = 0, Y = 0 };
10 : 11 : public static void Main() 12 : {
13 : // The reference is read-only, can't refer to something else
14 : Origin = new Point(); // compiler error!
15 : 16 : // HOWEVER, what it refers TO is not readonly!
17 : Origin.X = 7; // works!
18:
}
19:
}
Uh oh…. that’s not what we intended to happen! How is this possible? Remember that readonly applies to the field itself and not the value. This means that readonly value types can’t be changed at all. But with references, the only thing that is readonly is the reference itself!
Now, this may be the behavior you wish. You may simply wish to have your reference constant so it can’t refer to a new object. However, since C# does not let you apply const to reference types (like C++ does) the only way you can do this would be to make your class immutable. Essentially, this means that once the values are assigned in the class, they can never be changed. We can do this by taking the values all in the constructor, and only having private sets on our properties (yes, you can have an auto-property where the get is public and the set is private):
1 : public class Point
2 : {
3 : public Point(int x, int y) {
X = x;
Y = y;
}
4 : public int X { get; private set; }
5 : public int Y { get; private set; }
6:
}
7 : 8 : public static class Canvas
9 : {
10 : public static readonly Point Origin = new Point { X = 0, Y = 0 };
11 : 12 : public static void Main() 13 : {
14 : // Now can't change reference or what it refers to
15 : Origin = new Point(); // compiler error, reference is constant
16 : Origin.X = 7; // also compiler error, X setter is private
17:
}
18:
}
One final thing that makes readonly nice is that it can be applied to instance members as well as class (static) members. This means that you can provide a constant that applies for all instances of the class, or just for the current instance.
For example:
1 : public class Message
2 : {
3 : // this read-only is shared by all class instances since static
4 : private static readonly JmsAdapter _adapter = new JmsAdapter();
5 : 6 : // this constant is only for the current message, each message
7 : // will get their own _createdAt but once set at construction it
8 : // cannot be changed.
9 : private readonly DateTime _createdAt = DateTime.Now;
10:
}
Summary
So, we’ve investigated a lot of differences between readonly and const. So how do we apply this knowledge to make good decisions?
Use const when:
- The constant is a compile-time value, AND
- The constant’s domain does not change or is limited in scope to a single assembly, class, or method.
And use readonly when:
- The constant cannot be determined at compile-time, OR
- The constant is a non-string, non-null class, OR
- The constant is astruct, OR
- The constant is instance-level (instead of static).
Print | posted on Thursday, July 01, 2010 5:28 PM | Filed Under [ My BlogC#Software.NETFundamentals ]