Adding the const Keyword to C#
Today’s article is not about the const
keyword that C# already has. It’s about the const
keyword that C++ has and how we can approximate it in C# to make our code safer. It’s a really powerful tool that’s often the default for C++ programmers, but we can take advantage of a similar strategy in C#. Read on to learn how!
Want to pass an object to a function and make sure that function can’t change it? Just add const
to the parameter list like this:
Vector3 Add(const Vector3& a, const Vector3& b) { return Vector3(a.x+b.x, a.y+b.y, a.z+b.z); }
That const
keyword keeps us from being able to (accidentally?) modify the fields of the parameter. It puts the caller at ease. There’s simply no way the function can change the object being passed in.
Brief note for those of you unfamiliar with C++, the &
means that a reference is passed instead of a copy.
Want to return an object and make sure the caller can’t change it? Just return a const X
instead of a regular X
like this:
class Player { private: Vector3 position; public: const Vector3& GetPosition() { return position; } };
The simple addition of the const
keyword to the return type makes Player
able to return its position without fear that it’ll be modified by anyone calling GetPosition
. There’s no need to make a copy just for safety’s sake since the compiler won’t let you modify the return value:
Player player; const Vector3& pos = player.GetPosition(); pos.x = 123; // compiler error, pos is const!
Sadly, we have no such const
keyword in C#. We only have a const
for constant values such as integers, booleans, and strings. It’s essentially equivalent to using static readonly
with each value inlined. Clearly that’s a whole other meaning of const
.
So how can we approximate the C++ const
? We definitely can’t add a keyword to the language, so we’ll need to make use of other language features to achieve our goal. Let’s start by looking at an example class:
class Player { public int Health { get; set; } }
This very basic Player
only offers two options: get the Health
or set it. Getting it is a read-only operation that can be done on a constant Player
. Setting it changes the state of the Player
by writing to one of its fields and shouldn’t be allowed on a constant Player
.
The next step is to add an interface that describes the public API of the class:
interface IPlayer { int Health { get; set; } } class Player : IPlayer { public int Health { get; set; } }
This decouples the usage of players from the concrete Player
implementation. Any code using an IPlayer
can use any class that implements the interface. This is really handy for swapping in other types of players (e.g. networked players) or fakes during unit testing (e.g. mocks, substitutes).
Finally, we make one more interface. We fill it in with everything in IPlayer
that can be done with a constant player. We also take this out of IPlayer
.
interface IReadOnlyPlayer { int Health { get; } } interface IPlayer : IReadOnlyPlayer { int Health { set; } } class Player : IPlayer { public int Health { get; set; } }
We still only have one class, but two interfaces to view it with. We can view it in read-write mode with IPlayer
or read-only mode with IReadOnlyPlayer
. Any time an IPlayer
is required we can use a Player
. Likewise, any time an IReadOnlyPlayer
is required we can use either an IPlayer
or a Player
. Let’s see an example:
void PrintPlayerStatus(IReadOnlyPlayer player) { Debug.Log("player has " + player.Health + " health"); } var player = new Player(); player.Health = 123; PrintPlayerStatus(player); // no way player is modified here
Since PrintPlayerStatus
takes an IReadOnlyPlayer
, there’s no way it can modify it. It can’t set Health
because there’s no Health
setter on IReadOnlyPlayer
. The parameter is essentially const
now. This means that the code calling PrintPlayerStatus
can rest easy knowing that the player it passes won’t be modified in any way. There’s no need to carefully read through PrintPlayerStatus
to make sure it’s not writing to it.
Now let’s look at a return value example:
class Game { private IPlayer[] players; public IReadOnlyPlayer HealthiestPlayer { get { return players[0]; } // assuming the list is sorted by Health } } var game = new Game(); var player = game.HealthiestPlayer; player.Health = 123; // error, player has no Health setter
The Game
class is free to return its players as IReadOnlyPlayer
without worrying about what the caller will do with them. The players make up its internal state, so returning a full IPlayer
would break the class’ encapsulation. When returning an IReadOnlyPlayer
though, there’s simply no way for callers to modify the Game
‘s contents.
This is a pretty simple technique you can use with your classes and interfaces to approximate C++’s const
keyword. It doesn’t take much work to break the interface into two parts and shouldn’t add any runtime cost at all. In fact, this is the way that .NET does it with interfaces like IReadOnlyCollection in later versions than we get with Unity’s old Mono.
Do you use something similar? Do you find any value in it? Let me know what you think of the technique in the comments!
#1 by FH on May 2nd, 2016 ·
Why not just
public int Health { get; private set; }
for the player?
#2 by Walker on May 2nd, 2016 ·
That only lets the Player class modify Health. The goal here is to optional expose writeable state outside the class is declared in. If you don’t want to expose it outside the class then private set is a good way to go.
#3 by jackson on May 2nd, 2016 ·
That’s pretty much it. To illustrate, you can have a function that makes the player take damage by passing a read-write
IPlayer
:Or you can have a function that sorts players by health by passing a read-only
IReadOnlyPlayer
:If health had a
private set
then you couldn’t change it from functions likeApplyDamage
.