question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: 'readonly' for Locals and Parameters

See original GitHub issue

(Note: this proposal was briefly discussed in #98, the C# design notes for Jan 21, 2015. It has not been updated based on the discussion that’s already occurred on that thread.)

Background

Today, the ‘readonly’ keyword can be applied to fields; this has the effect of ensuring that a field can only be written to during construction (static construction in the case of a static field, or instance construction in the case of an instance field), which helps developers avoid mistakes by accidentally overwriting state which should not be modified. Optimizations are also possible in a compiler based on the knowledge that the field is immutable after construction.

Here’s an example of a class defined with a readonly field. The ‘m_birthDay’ field is explicitly declared by the developer to be readonly, which means any attempt to set it after construction will cause a compile-time error. The ‘FirstName’ and ‘LastName’ properties, which are defined with getter-only auto-props (introduced in C# 6), also result in readonly fields generated implicitly by the compiler, since there’s no need for a field to be writable if there’s no way for code to set it.

public class Person
{
    readonly DateTimeOffset m_birthDay; // readonly field, assigned in constructor

    public Person(string firstName, string lastName, DateTimeOffset birthDay)
    {
        FirstName = firstName;
        LastName = lastName;
        m_birthDay = birthDay;
    }

    public string FirstName { get; }  // getter-only auto-prop, backed by readonly field
    public string LastName { get; }

    public TimeSpan Age => DateTime.UtcNow – BirthDay;
    public string FullName => "\{FirstName} \{LastName}";

    public DateTime BirthDay
    {
        get => m_birthDay;
        set => m_birthDay = value; // Error: can’t assign to readonly field outside of ctor
    }
}

Problem

Fields aren’t the only places developers want to ensure that values aren’t mutated. In particular, it’s common to create a local variable to store temporary state, and accidentally updating that temporary state can result in erroneous calculations and other such bugs, especially when such “locals” are captured in lambdas.

Solution: readonly locals

Locals should be annotatable as ‘readonly’ as well, with the compiler ensuring that they’re only set at the time of declaration (certain locals in C# are already implicitly readonly, such as the iteration variable in a ‘foreach’ loop or the used variable in a ‘using’ block, but currently a developer has no ability to mark other locals as ‘readonly’).

readonly long maxBytesToDelete = (stream.LimitBytes - stream.MaxBytes) / 10;
...
maxBytesToDelete = 0; // Error: can’t assign to readonly locals outside of declaration

This is particularly valuable when working with lambdas and closures. When an anonymous method or lambda accesses local state from the enclosing scope, that state is captured into a closure by the compiler, which is represented by a “display class.” Each “local” that’s captured is a field in this class, yet because the compiler is generating this field on your behalf, you have no opportunity to annotate it as ‘readonly’ in order to prevent the lambda from erroneously writing to the “local” (in quotes because it’s really not a local, at least not in the resulting MSIL). With ‘readonly’ locals, the compiler can prevent the lambda from writing to local, which is particularly valuable in scenarios involving multithreading where an erroneous write could result in a dangerous but rare and hard-to-find concurrency bug.

readonly long index = …;
Parallel.ForEach(data, item => {
    T element = item[index];
    index = 0; // Error: can’t assign to readonly locals outside of declaration
});

Solution: readonly parameters

As a special form of local, parameters should also be annotatable as ‘readonly’. This would have no effect on what the caller of the method is able to pass to the parameter (just as there’s no constraint on what values may be stored into a ‘readonly’ field), but as with any ‘readonly’ local, the compiler would prohibit code from writing to the parameter after declaration, which means the body of the method is prohibited from writing to the parameter.

public void Update(readonly int index = 0) // Default values are ok though not required
{
    …
    index = 0; // Error: can’t assign to readonly parameters
    …    
}

This support for ‘readonly’ parameters would be particularly helpful for a very specific usage of parameters: passing structs by reference. When a reference type is passed to a method, the state that’s actually passed into the method is a copy of the reference to the object (the reference is passed by value), effectively a pointer-sized piece of data. In contrast, when a struct is passed into a method, a copy of the struct is passed in (the struct’s state is passed by value). If the struct is small, such as is an Int32, this is perfectly fine and there’s little better that could be done by the developer. However, when the struct is large (for example, the System.Windows.Media.Media3D.Matrix3D struct contains 16 doubles, making it 128 bytes in size), it can become quite expensive to continually pass around copies of the data. In these cases, developers often resort to passing structs by ref, passing a pointer to the original data rather than making a copy. This avoids the performance overhead, but it now inadvertenly enables the called method to update the original struct’s value:

public void Use(ref Matrix3D matrix)
{
    …
    matrix.M31 = 0; // Oops! We wanted performance, not more bugs.
    …
}

By enabling marking parameters ‘readonly’, the struct could be passed by reference but still prevent the callee from mutating the original value:

public void Use(readonly ref Matrix3D matrix)
{
    …
    matrix.M31 = 0; // Error: can’t assign to readonly parameters
    …
}

This would also enable readonly struct fields to be passed by ref (to a readonly ref parameter), whereas in C# today passing a readonly struct field by ref is never allowed. As with ‘readonly’ on fields, ‘readonly’ as it applies to locals and parameters would be shallow rather than deep, meaning that it would prohibit writing to the readonly field, but it wouldn’t prevent mutation of any state contained in objects referenced from the ‘readonly’ value.

Additional Considerations

In many situations, in particular when “var” is used to declare a local with type inference, the developer often would like ‘readonly’ behavior for that local. This is possible if ‘readonly’ is allowed on locals, e.g.

readonly int foo = ...;
readonly var bar = ...;

but it makes the desired immutability harder to express than mutability. To combat that, as shorthand for ‘readonly var’, we could also introduce a new ‘let’ or ‘val’ syntax, with the same meaning as ‘readonly var’:

val foo = ...;
val bar = ...;

Issue Analytics

  • State:closed
  • Created 9 years ago
  • Reactions:96
  • Comments:94 (18 by maintainers)

github_iconTop GitHub Comments

18reactions
ryanceriumcommented, Jan 28, 2015

I’d like to allow the readonly modifier on classes to allow immutability by default, and methods also to correlate with C++ const functions.

readonly class Point
{
  public int x; // Implicit readonly
  public int y; // Implicit readonly
  Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
}

Point origin = new Point(0, 0);
origin.x = 1; // Error:  can't reassign a readonly class member
5reactions
binkicommented, Sep 26, 2018

@TahirAhmadov I agree with you that defining let as equivalent to readonly var is confusing. For older languages, I myself only konw of BASIC which had a LET statement which actually mutated a variable (though it was not a declaration itself). For modern languages which those coming to C# would know, modern JavaScript uses let for declaring a mutable variable. If let is adopted, web developers with C# backends will have to keep straight that let is assignable in one context and readonly in the other.

I do have an issue with your suggestion of readonly a = 123;. In the existing precedent of readonly for class variables, it is always followed by a type. readonly object o = "str"; and readonly var s = "str"; make sense to me, but readonly o = "o"; both departs from the existing class variable syntax and would introduce ambiguity if explicitly specifying the variable’s type were allowed (which it should be for consistency with class variables).

However, a shorthand for readonly var is necessary to get developer buy-in. People use var because it is short—they would avoid using readonly var just because it requires more typing and clutters the screen/code. Also, F# and LINQ are precedents of using let to declare readonly variables. Unless you can come up with a different keyword that is both short, easy to use, and also conveys the meaning more clearly than let, I myself am going to continue hoping that let is introduced as readonly var.

This issue is dead though…

Please see https://github.com/dotnet/csharplang/issues/188 and keep the discussion there. There has already been some discussion over let there.

Read more comments on GitHub >

github_iconTop Results From Across the Web

C# Futures: Read-Only Local Variables - InfoQ
The basic syntax for read-only local variables is to simply prepend the type name with the readonly keyword. This would work in local...
Read more >
Why does C# disallow readonly local variables? [closed]
Readonly means the only place the instance variable can be set is in the constructor. When declaring a variable locally it doesn't have...
Read more >
readonly keyword - C# Reference
The readonly keyword is a modifier that can be used in four contexts: In a field declaration, readonly indicates that assignment to the...
Read more >
SE-0311: Task-local values - Proposal Reviews
Hello, Swift community. The review of SE-0311: Task-local values begins now and runs through April 26th, 2021. This proposal is part of the ......
Read more >
Edit a field - ITOM Practitioner Portal
This property is read-only. Enable sort, If selected, a user may sort the field when it appears in a record list by clicking...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found