Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News C# Futures: Immutable Variables

C# Futures: Immutable Variables

Leia em Português

In C#, the readonly keyword can only be used at the field level. Under proposal 115, Readonly for Locals and Parameters, this would be extended to cover many more scenarios.

The proposal begins with the ability to create read-only locals. The first use case for this is simply documentation. By marking a local as read-only, it indicates that the local is not and should not be changed elsewhere in the function. This is especially useful for longer, more complex functions that can’t be seen all at once.

The second use case is safety when working with multi-threading and closures. If you execute Parallel.ForEach on a closure, it is easy to introduce a race condition. If you mark all locals as read-only by default, then any mutable locals will stand out as something to be reviewed.

Syntax Concerns

Read-only locals provide more value when used by default, but for that to happen the syntax cannot be burdensome. Consider these lines:

var gravity = 9.780327;
double gravity = 9.780327;
const double gravity = 9.780327;

Even though gravity is intended to be a constant, most developers prefer to use the first version. They don’t do the “right thing” because it is easier to type and, in an isolated case, doesn’t really matter that much.

This preference for ease over correctness occurs in casting as well. The code below is a very common mistake even with experienced programmers.

var button = sender as Button;
button.Enabled = false;

The code should be “button = (Button)sender”, but again the correct code is slightly harder to type.

To address these concerns, the proposal is suggesting a shorthand when using implicitly typed locals. Two keywords are currently being considered:

val gravity = 9.780327;
let gravity = 9.780327;

Between the two, “let” is currently favored because it is already used in LINQ expressions and is easier to visually differentiate from “var”. (It is also better for non-English speakers whose native language doesn’t distinguish between r and l.)

Read-only Parameters

Next up is the ability to mark parameters as read-only. Users of Visual Studio’s code analysis tools probably think this is redundant, as it automatically prevents changing the value of normal parameters. However, there is use case it doesn’t cover.

When working with high performance code, it is not unusual to prefer structs over classes, even when those structs are large. To avoid copying costs, those structs are passed to functions using ref parameters.

From a documentation standpoint, there is nothing in the function signature that makes it clear to the caller that the parameter won’t be modified. The ability to mark the parameter as “readonly ref” would plug that hole.

Not everyone is happy with that syntax, because it would require decorating the callsite with “ref”, which would be somewhat misleading. So Porges suggested an “in” keyword instead:

void DoSomething(readonly ref LargeStruct value)
DoSomething(ref myLocal);
void DoSomething(in LargeStruct value)

Readonly and Const

While read-only prevents the outright replacing of a value, it usually doesn’t prevent one from modifying an object’s members. So an extension to this proposal is the ability to extend the protection offered by read-only.

There are languages such as C++ that already support this concept. While they do work as advertised, they can be hard to use correctly because developers are often confused about whether const is being applied to the variable itself of the contents of the variable. So avoiding that ambiguity is important when working out the syntax. One suggestion is to use “readonly” for the variable and “const” for the contents.

C++ also offers the concept of a const function. This is a function that can be called via a readonly/const variable because it is guaranteed to not change the state of the object. .NET also has this concept via the Pure attribute, but it is not currently honored by the C# compiler.

Footnote: Readonly, Structs, and Fields

Though currently not part of the proposal, the interaction between readonly, structs, and fields should be considered. Consider this line:

private readonly Foo _foo = new Foo(1, 2, 3);

If Foo is fully immutable, this readonly field works as expected. But if there are any setters on Foo, then each time you read from this field the compiler makes a copy so that you can’t accidentally modify it. So unlike readonly reference fields, readonly struct fields really are readonly. But since this has a hidden performance cost and inconsistency, finding a cleaner way to express this concept would be beneficial.

For more information, see Mutating Readonly Structs by Eric Libbert.

Rate this Article