readonly modifier is used to declare an instance-specific constant data member which can be initialized in the class constructor and is not limited to compiler-time constants only, providing more flexibility than using the
const modifier implicitly defines the data member as static, and must be initialized along with its declaration, as follows:
If the const is not initialized along with the declaration, the compiler will generate an error. Moreover, a const data member must be initialized with a compile-time constant – in other words there is no way to initialize a constant using a runtime value. For instance, the following code won’t compile:
1 2 3 4 5 6
as the compiler will inform that:
The advantage of defining a data member as
const is that it cannot be changed – the drawback being the inability to initialize the field in a different place other than the declaration.
On the other hand, the
readonly modifier extends the const modifier by allowing the data member to be initialized in any class constructor. This allows a data member to be initialized when a class is being instantiated, preventing it from being modified in any other class method – in other words, once the const data member is initialized it never changes during the class instance lifetime.
As opposed to the
const modifier, the
readonly modifier creates an instance-specific data member, so each class instance will have its own readonly data member instances. This doesn’t mean that a readonly data
member cannot be static – simply it must explicitly be declared as such.
All the following declarations and initializations are valid:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
The member declared at line 4 is initialized in the constructor with a string constant, whereas the
int member declared at line 3 is initialized both in the declaration and in the constructor. In this case, the constructor initialization takes precedence, as it is executed after the data member instantiation.
The following case instead is not valid:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
That’s because the
readonly member is initialized in a class member which is not a constructor, even if this method is called from the constructor. The reason why this is not allowed is that the
Init() method can virtually be called from anywhere within the class, or even outside if it is protected, internal or
public rather than private.
Another advantage of the
readonly modifier is that it can be initialized with a runtime value or an expression evaluated at runtime, for instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
As we can expect, the
_myIntField readonly data member will be initialized to
10 * 5 = 50.
Readonly data members are not limited to basic data types – it can be used for class instances. For example, when writing a database accessor class we might want to provide a database connection:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
The above code would work without declaring the
_connection data member as readonly – but in such case the connection variable can be changed anywhere in the class.
It’s important to keep in mind that when a
readonly data member is of a reference type (i.e. instance of a class and not either a struct or a base data type) the reference is constant, but the instance it points to is not. So the internal status of a readonly instance can change and can be changed, as proven by the following sample:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
If this code is compiled and run, it will generate the following output:
Thanks to what Chris Marisic highlighted in his comments below, I need to clarify a few points to prevent some misleading assumptions.
readonly members can be modified using reflection. The
readonly modifier is a compiler directive, so any attempt to modify a readonly data member is detected during compilation. A consequence is that using reflection it’s possible to modify a
readonly data member
#2 – I wrote above that the
const modifier “allows a data member to be initialized when a class is being instantiated, preventing it from being modified in any other class method”. This statement doesn’t mean that the data member is immutable, at least not always. .NET supports two kinds of data types: value types and reference types:
* value types are allocated on the thread’s stack and they hold the actual value
* reference types are allocated on the thread’s stack and they hold a pointer to an object allocated in the managed heap
In both cases variables are allocated on the thread’s stack, the difference being that a value type holds the actual value (for example, an
struct), whereas the reference type holds a pointer to the memory area where the actual value is stored (for example, an instance of a
When a value type is declared as
readonly, its value is immutable, meaning that once assigned it cannot be modified outside constructors.
When a reference type is declared as readonly, the pointer is immutable, but not the object it points to. This means that:
a reference type data member can be initialized in order to point to an instance of a class, but once this is done it’s not possible to make it point to another instance of a class outside of constructors
readonlymodifier has no effect on the object the readonly data member points to.
Maybe a real example helps to better understand the difference. Let’s create a simple class and a simple struct. They have one int data member only.
1 2 3 4 5 6 7 8 9
Now let’s use them in a test class:
1 2 3 4 5 6 7 8 9 10 11 12 13
_myClass data member is a reference type, so it must be initialized by allocating a new instance of
MyClass and assigning its pointer, as seen at line 8. On the other hand, the
_myStruct data member is a value type, meaning that it is already an instance of
MyStruct. Initialization of their respective
fields are done at lines 9 and 11.
Let’s add a method to the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
If we try to compile this code, we get 2 errors:
1 2 3 4
MyStruct is a struct, hence a value type;
_myStruct is immutable since declared as
readonly, so any of its data members cannot be modified outside of a constructor.
MyClass is a class, hence a reference type;
_myClass is immutable since declared as readonly, so it cannot be modified in order to point to another instance of a class (or to
null) outside of a constructor.