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!