C# has a ton of keywords, and it is sometimes hard to keep track of
them all. One keyword that often gets lost in the shuffle is the
readonly
keyword, often because many people just group it with the
const
keyword and leave it at that. Well, the readonly
keyword
deserves better than that - and so here today we are going to talk about
what exactly it does, how it differs from const
, and why you would
ever want to use it at all.
The
readonly
keyword does exactly what its name states - it makes a
field read only. Well, I guess it doesn't quite mean just that - because
there is one point in the lifespan of an object that a readonly
field
can be set. That point is during object initialization. And this fact
right here is why readonly
differs from const
- a const
field gets
determined at compile time, while a readonly
field is determined at
runtime. For example:public class MyClass
{
public const string foo = "foo";
public MyClass()
{
foo = "bar";
}
}
//Error: The left-hand side of an assignment must be a variable, property or indexer
Here we have
foo
which is a constant. We can only set it in its
declaration, anywhere else (including the constructor, as we see here)
will result in a compile error. But with the readonly
keyword, the
above code compiles just fine:public class MyClass
{
public readonly string foo = "foo";
public MyClass()
{
foo = "bar";
}
}
This compiles because it is legal to set a
readonly
field at any point
during the initialization of an object - both up in the field
declaration and in the constructor. This means that we can actually
dynamically determine the value of a readonly field at runtime by
passing it in the constructor, or perhaps calculating it in the
constructor:public class MyClass
{
public readonly string foo = "foo";
public MyClass(string abc)
{
foo = abc;
}
}
But if we try and set a
readonly
field at any other point, we get a
compile error:public class MyClass
{
public readonly string foo = "foo";
public MyClass(string abc)
{
foo = abc;
}
public void SetFoo(string newFoo)
{
foo = newFoo;
}
}
//Error: A readonly field cannot be assigned to (except in a constructor or a variable initializer)
One somewhat surprising case at first (but not so surprising after you
think about it a bit) where we cannot set a
readonly
has to do with
inheritance. For example:public class MyClass
{
public readonly string foo = "foo";
public MyClass(string abc)
{
foo = abc;
}
}
public class MyExtClass : MyClass
{
public MyExtClass(string abc1, string abc2)
: base(abc1)
{ foo = abc2; }
}
//Error: A readonly field cannot be assigned to (except in a constructor or a variable initializer)
You cannot set a
readonly
belonging to a base class in the constructor
for the current class. This makes sense after a moment of thought - the
base class is already initialized (its constructor has already been run)
and so letting a readonly
field be modified here undermines what the
readonly
keyword is supposed to be accomplishing.
Ok, so now that we know what the
readonly
keyword is and what it does,
why should we use it? There are a number of reasons, and they all have
to do with code that is easier to read and think about. Sadly, there are
no performance benefits to using the readonly
keyword - none that I
can find, anyway. This is somewhat perplexing - one would think there
would be some performance benefits from using immutable variables-
perhaps one of our readers will be able to tell me differently.
But anyway, back to the reasons you should use the
readonly
keyword.
For instance, take the following code:public class MyClass
{
private string _foo = "foo";
public MyClass(string foo)
{
_foo = foo;
}
public string Foo
{
get { return _foo; }
}
}
There is nothing actually wrong with this code - we have a private
string
_foo
and we expose it as a property Foo
, only creating a
getter. This essentially exposes a read only version of _foo. But if
_foo
isn't going to be changing internal to the class either, it is
much nicer to write the code this way:public class MyClass
{
public readonly string foo = "foo";
public MyClass(string foo)
{
foo = foo;
}
}
Here, we lose the property altogether, and instead just have a public
read only field. Another reason to use the
readonly
keyword is that objects that are
immutable are much easier to reason about. For example, here we have two
objects, a three dimensional point object, and an element that needs to
store its own position and notify others of changes:public class Vector3
{
public event EventHandler ValueChanged;
private float _x;
private float _y;
private float _z;
public Vector3(float x, float y, float z)
{
_x = x;
_y = y;
_z = z;
}
private void OnValueChanged()
{
if (ValueChanged != null)
ValueChanged(this, EventArgs.Empty);
}
public float X
{
get
{
return _x;
}
set
{
if (_x == value)
return;
_x = value;
OnValueChanged();
}
}
public float Y
{
get
{
return _y;
}
set
{
if (_y == value)
return;
_y = value;
OnValueChanged();
}
}
public float Z
{
get
{
return _z;
}
set
{
if (_z == value)
return;
_z = value;
OnValueChanged();
}
}
}
public class Element
{
public event EventHandler PositionChanged;
private Vector3 _position = null;
public Element(Vector3 pos)
{
Position = pos;
}
private void OnPositionChanged()
{
if (PositionChanged != null)
PositionChanged(this, EventArgs.Empty);
}
void Position_ValueChanged(object sender, EventArgs e)
{
OnPositionChanged();
}
public Vector3 Position
{
get { return _position; }
set
{
if (_position == value)
return;
if (_position != null)
_position.ValueChanged -= new EventHandler(Position_ValueChanged);
_position = value;
OnPositionChanged();
if (_position != null)
_position.ValueChanged += new EventHandler(Position_ValueChanged);
}
}
}
That's a bunch of code! Mostly because we have to keep checking values
and triggering events, so that we make sure that the final
PositionChanged
event always gets fired if anything about the position
changes. Plus we have to worry about someone holding on to our Vector3
object for this element and passing it along, and passing it along,
until it reaches the hands of someone who shouldn't be able to modify
our position at all.
So lets take a look at how this code looks with the use of the
readonly
keyword, making the Vector3
object immutable:public class Vector3
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public Vector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
}
public class Element
{
public event EventHandler PositionChanged;
private Vector3 _position = null;
public Element(Vector3 pos)
{
Position = pos;
}
private void OnPositionChanged()
{
if (PositionChanged != null)
PositionChanged(this, EventArgs.Empty);
}
public Vector3 Position
{
get { return _position; }
set
{
if (_position == value)
return;
_position = value;
OnPositionChanged();
}
}
}
Wow, that's a lot less code! With this new code, we no longer need the
event stuff all over the
Vector3
object. Plus, we don't have to worry
about who is holding on to the object that contains an element's
position - because they won't actually be able to modify it. To give an
element a new position now, you actually need to hand it a new Vector3
object.
Well, I hope you've enjoyed this discussion about the
readonly
keyword.
Comments
Post a Comment