Readonly parameters in C# - a step closer to immutability

I’ve been working with C# for many years now and the new additions to the language are very exciting. They’re focused on immutability. Well, actually, the Microsoft documentation seem to suggest they’re more focused on Performance, but they go hand-in-hand; the less you can modify, the faster things are.

The benefits of immutability are often overlooked especially by those who have only experienced programming in C#. But coming from a C++ background, I long for the immutability that C++ has. For example, In C++, you can:

(these bullet points are generalisations)

const is great. There’s even been 80’s pop songs written on the virtues of const:

The new features in C# 7.2 are losely related to the above. This post describes one of them; in parameters

in parameters #

Here’s a method that takes an in parameter:

public void DoStuffTo(in Thing thing)

It says that the DoStuffTomethod takes a Thingand cannot modify it. If you try, you’ll get:

This is fantastic! Immutable parameters; I know that my method is immutable and I know that I don’t want to mutate any parameter.s And now, when I accidentally do, I get a compiler error.

The example above clearly violates the intent that you wanted a readonly variable, but what about situations where it isn’t clear that doing something mutates the object? What about this?

public void DoStuffTo(in Thing thing)
{
thing.Conculate();
}

This is fine. No complaints from the compiler. You might naturally think that Conculate doesn’t mutate the instance…

Let’s look at the implementation:

struct Thing
{
public Thing(int id)
{
Id = id;
}

public int Id { get; set; }

public string SayHello() => $"Hello from {Id}";

public void Conculate()
{
Id = 42;
}
}

Wait! Conculate does mutate state?! How could it, our parameter is readonly! No compiler errors like when we set the property… This is because the compiler doesn’t know that the method mutates state.

But all is not what it seems. Let’s run the following and see what the output is:

static void Main(string[] args)
{
var thing = new Thing(1);
DoStuffTo(thing);
}

static void DoStuffTo(in Thing thing)
{
thing.Conculate();
Console.WriteLine(thing.SayHello());
}

… here’s the output…

We called Conculate, which we know mutates the state (by setting Idto 42), but it turns out that a copy was made and the IDwas set on that copy.

Here’s an excerpt from the Microsoft page on reference semantics with value types:

So, it turns out that readonly parameters aren’t as powerful as C++ const parameters. They are, technically, read-only (you can’t mutate them), but they’re not const as in C++ const.

I also don’t like the fact that you can legally call methods that mutate the instance, but don’t know about it. If constmethods existed in C#, the compiler would’ve disallowed the call to Conculate because it couldn’t be declared as const (as it mutates a field).

Perhaps in a future version of C#, we’ll get **const**methods…

But there’s more… #

Even though readonly parameters are a step in the right direction, I was disappointed. I was dissapointed because when I read about readonly parameters, I wanted to immediately jump into every single method I’ve ever written and splash in keywords on every parameter. But the majority of things passed around are classes and not structs. Notice in the earlier example that Thing was a struct. In time, with the performance benefits of reference semantics with value types, I’m sure struct will be the go-to type instead of class.

Anyway, in parameters can be either struct or class. Here’s what happens when you change Thing to be a class rather than a struct; remember the earlier example where the compiler stopped you setting a property? Well, now it lets you do whatever you want:

Oh the disappointment!

Here’s another excerpt from the documentation:

The benefits are minimal‘. I personally think minimal is being generous. I would’ve use the word Dangerous. If Thing is a class rather than a struct, then what is passed is a reference to a reference. This means that the value that the method is dealing with can be changed to point to something else at any time! (even from outside of that method)

Here’s a (contrived) example:

static void Main(string[] args)
{
Thing current = new Thing(1);

var first = Task.Run(()=>DoStuffTo(current));
var second = Task.Delay(1000).ContinueWith(t => current = null);

Task.WaitAll(first, second);

Console.ReadLine();
}

static void DoStuffTo(in Thing thing)
{
Console.WriteLine($"Press enter to process {thing.Id}");

Console.ReadLine();

Console.WriteLine(thing.SayHello());
}

Here, we start off a task to do something with a Thing. We wait 1 second and set the reference to null. Assuming the user hasn’t pressed Enter yet, a NullReferenceException will happen when they do. This is because we’re passing a reference to a reference and then setting the reference to null.

I can only imagine all the subtle bugs that this will cause if spread throughout a codebase.

ReSharper to the rescue? #

I don’t even know if it’s possible, but I’d have liked the compiler to disallow in parameters that aren’t value types. I’m sure the ReSharper team are working on a new code-inspection to warn when passing in references.

🙏🙏🙏

Since you've made it this far, sharing this article on your favorite social media network would be highly appreciated 💖! For feedback, please 🦋 ping me on Bluesky! 🦋

Leave a comment

Comments are moderated, so there may be a short delays before you see it.

3 comments on this page

  • Andrei

    commented

    Where is the code you're talking about? Conculate and stuff?
    I just couldn't follow the article due to not having what to look at.

  • Claudio M. Zanella

    commented

    Thank you for sharing your experience, the application of the CONST statement in a parameter was always my intrigue and thanks to your article I was able to implement it.

  • Steve

    commented

    Andrei - the code presented in the post is trivial, but the Conculate method is provided (as a trivial example of something misleadingly named that mutates state)

Published