# `readonly` fields

> A `readonly` field can be assigned only where it is declared or in a constructor of the class that declares it. Everywhere else — a method of the same class, a subclass constructor, an object initializer, any code holding the value — a write is a compile error. The field itself is ordinary: it holds a per-instance value chosen at construction, unlike a `const`, whose value is fixed when the code is compiled.

<!-- id: class-readonly · area: class · stability: stable · html: https://osysharp.com/reference/class/readonly/ -->

## Summary        {#summary}
`readonly` marks a field that only the constructor may set:

```osy title="a field the constructor fixes" test app=class-readonly
class Booking {
  public readonly string Reference;
  public readonly int Seats;

  public Booking(string reference, int seats) {
    Reference = reference;
    Seats = seats;
  }
}
```

Once the object exists, the field is settled — every other write is refused at compile time.

## Signature      {#signature}
```osy syntax
readonly T Name;                // set by a constructor
readonly T Name = value;        // set at the declaration
public readonly T Name;         // combines with any visibility
```

## Description    {#description}

### The two places a `readonly` field may be assigned   {#where}
There are exactly two, and they are the same two as in C#:

1. **its own declaration** — `public readonly decimal Rate = 0.25m;`
2. **a constructor of the class that declares it**

```osy title="both legal writes" test app=class-readonly
class Invoice {
  public readonly decimal Rate = 0.25m;      // 1. at the declaration
  public readonly string Number;

  public Invoice(string number) {
    Number = number;                          // 2. in the constructor
  }
}
```

Both values are ordinary per-instance data — read them like any other field:

```osy title="a readonly field holds a normal runtime value" run app=class-readonly
[Test]
void ReadOnly_Fields_Hold_Their_Values() {
  var i = new Invoice("INV-1");
  Assert.Equal("INV-1", i.Number);
  Assert.Equal(0.25m, i.Rate);
}
```

### Assigning it anywhere else is a compile error   {#refused}
The refusals are the feature. None of these compile:

```osy syntax
var i = new Invoice("INV-1");
i.Number = "INV-2";                  // ✗ a write from outside
i.Rate += 0.1m;                      // ✗ `+=` is a write too

var j = new Invoice { Number = "x" };   // ✗ an object initializer runs after the constructor

class Invoice {
  public void Renumber(string n) {
    Number = n;                      // ✗ a METHOD of the same class — only a constructor may write
  }
}
```

The last one is worth pausing on: `readonly` narrows the write to **constructors**, not to the class. A method of the
declaring class is refused exactly like outside code.

### A subclass constructor cannot write the base's field   {#inheritance}
A field belongs to the class that declares it. By the time a derived constructor's body runs, the base has already
been constructed and its `readonly` fields are settled — so a subclass may write its **own** readonly fields and not
its base's:

```osy title="each class writes the fields it declares" test app=class-readonly
class Badge {
  public readonly string Tag;
  public Badge(string tag) { Tag = tag; }
}

class Ranked : Badge {
  public readonly decimal Rank;

  public Ranked(string tag, decimal rank) : base(tag) {
    Rank = rank;                     // its own — fine
  }
}
```

Writing `Tag = "x"` inside `Ranked`'s constructor is a compile error: pass the value to `base(…)` instead, which is
what the example does.

### `readonly` is not `const`   {#versus-const}
They read similarly and mean different things:

| | `const` | `readonly` |
|---|---|---|
| when the value is chosen | when the code is compiled | when the object is constructed |
| can it differ per instance | no — there is one value | yes — each `new` may pass a different one |
| what it may be initialized with | a compile-time constant | any expression the constructor can evaluate |
| is there a per-instance slot | no, uses are replaced by the value | yes, it is a real field |

Reach for `const` for a fixed number or name the whole program shares, and `readonly` for a value each instance is
given once and then keeps. Writing both on one field is a compile error — a `const` has no instance slot to protect.

### `readonly` applies to a field, not a property   {#not-a-property}
A property has no storage of its own, so there is nothing for `readonly` to narrow. The property spellings that mean
the same things are:

- `{ get; }` — an [auto-property](https://osysharp.com/reference/class/properties/) only a constructor may set
- `{ get; init; }` — settable during construction, including from an object initializer

```osy syntax
public readonly decimal Area { get; set; }   // ✗ readonly applies to a FIELD
public decimal Area { get; }                 // ✓ only the constructor sets it
public decimal Area { get; init; }           // ✓ an object initializer may too
```

### Can an entity field be `readonly`?   {#entities}
`readonly` is a `class` modifier. An entity's fields are stored rows written by data operations, and what may write
them is declared in its `security { }` block rather than by a member modifier.

## See also       {#see-also}
- [class properties](https://osysharp.com/reference/class/properties/) — `init`, `required`, and the property forms that express single-assignment
- [constructor](https://osysharp.com/reference/class/constructors/) — the constructor, which is where a `readonly` field gets its value
- [Classes](https://osysharp.com/reference/class/index/) — fields, defaults, and what a class is
- [type visibility (public / internal)](https://osysharp.com/reference/types/visibility/) — `public`, `private` and `protected`, which combine with `readonly`
