Declarations
readonly fields
Fix an instance field once, either in its initializer or in every constructor, and keep it read-only afterwards.
since 0.1.0-alpha.1linuxwindowswasm
Description
readonly is the construction-time counterpart of const. It applies to instance fields only:
a field that is set once while the object is being built and cannot change afterwards. Use it for
values that are only known at construction time, such as an injected dependency or a generated id.
class AuthController {
private readonly IUserService userService;
new AuthController(IUserService userService) this.userService = userService;
}
class Session {
readonly string id = generateId(); // initializer form; may be computed at run time
}
A readonly field is written in exactly one place: its initializer, or each of the declaring
class's constructors. After the object is built the field is read-only.
readonly in the constructor form and the initializer form
interface IUserService { string find(); }
class Real : IUserService { string find() => "ada"; }
class AuthController {
private readonly IUserService userService;
new AuthController(IUserService userService) this.userService = userService;
string who() => userService.find();
}
int nextId = 100;
int makeId() { nextId += 1; return nextId; }
class Session {
readonly int id = makeId();
readonly string tag;
new Session(string t) { tag = t; }
new Session() { tag = "default"; }
}
console.writeln(AuthController(Real()).who());
Session a = Session("a");
Session b = Session();
console.writeln(a.id);
console.writeln(b.id);
console.writeln(a.tag);
console.writeln(b.tag);
ada
101
102
a
default
Rules
- A
readonlyfield must be written exactly once. - Initializer form (
readonly T x = v;): the initializer is the one write. A constructor that also assignsxis a second write and a compile error. The initializer can be any expression. - Constructor form (
readonly T x;): every constructor the class declares must assignxexactly once. A constructor that does not assign it is a compile error, and so is a class with no constructor and no initializer, since nothing could assign the field. - The constructor assignment must be one of the constructor body's own top-level statements. An
assignment inside an
if/else, even one that covers both branches, is not recognized, and the constructor is rejected as not assigning the field. - Outside the write window, a write is a compile error: from an ordinary method, from another class,
after construction, or from a derived class that reaches the base's
readonlyfield directly. A derived class sets a base'sreadonlyfield by calling the base constructor. readonlyis not part of the type and is not transitive: the field itself is fixed, but the object it refers to can still change.- A
setaccessor cannot be declared over areadonlyfield, and a field cannot be bothconstandreadonly. - If two bases declare a field of the same name and type, one
readonlyand one not, the class that inherits both is ambiguous and a compile error. Resolve it withdistinctor by restating the field in the derived class.
Examples
Each of these is rejected at compile time:
Violations of write-once
class A { readonly int x; new A() { x = 1; x = 2; } } // assigned twice
class B { readonly int x = 1; new B() { x = 2; } } // initializer plus constructor
class C { readonly int x; new C() { } } // never assigned
class D { readonly int x; } // no constructor, no initializer
class E { readonly int x = 1; void reset() { x = 2; } } // outside the write window
class F { readonly int x; new F(bool c) { if (c) { x = 1; } else { x = 2; } } } // not a top-level statement
A readonly field sets a base class's value through the base constructor:
A derived class uses the base constructor
class Base {
readonly int id;
new Base(int i) { id = i; }
}
class Derived : Base {
new Derived() { Base::Base(42); }
}
console.writeln(Derived().id);
42
See also
- const declarations — Fix a local, field, global, parameter or loop binding after its initializer, with the exact rules for each kind of slot.
- Constructors — Declare constructors with new, select among them by label and argument types, call base constructors, and construct classes that declare none.
- Fields — Declare the data a class or struct holds, with optional initializers, and how a field without an initializer gets its default value.