Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Class

class properties

class <Name> { public <Type> <Prop> => <expr>; // computed (get-only) public <Type> <Prop> { get => …; set => …; } // full property public <Type> <Prop> { get; set; } // auto-property }

A class member that reads and writes like a field but runs a body on access — a computed value, a validating setter, or an auto-property whose storage the platform synthesizes. It is a method dressed as a field access, so it works everywhere a class does: server, client, and across a suspension.

stable13 examples compiled by CIclasslogic

Summary#

A class may declare properties — members that read and write like a field but run a body on access. A property is the natural home for a value derived from other fields (Label), for a write that must be validated or transformed, and for a field you want to expose with asymmetric access. It is, exactly as in C#, a pair of methods dressed as a field: a read obj.Prop runs the getter, a write obj.Prop = v runs the setter.

Use a plain field when a value is just stored; reach for a property when access itself is behaviour.

Signature#

class <Name> {
  public <Type> <Prop> => <expr>;                 // computed — read-only, no storage
  public <Type> <Prop> { get => <expr>; }         // getter-only, explicit
  public <Type> <Prop> { get => …; set => …; }    // full property over a backing field (`value` is the input)
  public <Type> <Prop> { get; set; }              // auto-property — the platform synthesizes the backing field
  public <Type> <Prop> { get; private set; }      // asymmetric — read anywhere, write only inside the class
  public <Type> <Prop> { get; init; }             // init-only — settable during construction, then frozen
  public required <Type> <Prop> { get; set; }     // required — must be supplied in every `new T { … }`
}

Description#

A computed property — a value derived from other fields#

The smallest property is getter-only: an expression over the instance's other fields, with no storage of its own. Written => expr, it recomputes on every read.

class Money {
  public decimal Amount;
  public string Currency;

  // computed on read — no backing storage
  public string Label => Amount.ToString("F2") + " " + Currency;
}

Reading it runs the getter over the current field values:

[Test]
void Label_Runs_The_Getter() {
  var m = new Money { Amount = 9.5m, Currency = "USD" };
  Assert.Equal("9.50 USD", m.Label);
}

A computed property is read-only — it has no setter, so a write to it is a compile error. That is the point: it is a view of other state, not a slot you can assign.

A full property — a getter and a setter over a backing field#

When a write needs to be validated or transformed, give the property both accessors over an explicit private field. Inside the setter, the incoming value is the implicit parameter value:

class Account {
  private decimal _rate;

  public decimal Rate {
    get => _rate;
    set {
      if (value < 0m) { throw "rate cannot be negative"; }
      _rate = value;
    }
  }
}

A write runs the setter; a read runs the getter:

[Test]
void Rate_RoundTrips_Through_The_Accessors() {
  var a = new Account();
  a.Rate = 0.2m;
  Assert.Equal(0.2m, a.Rate);
}

A bad write — a.Rate = -1m — runs the setter body and throws, exactly as the setter says. The setter is the one place the rule lives, so no caller can slip an invalid value past it.

An auto-property — the platform synthesizes the storage#

When the accessors would be trivial — read the field, write the field — write { get; set; } and the platform synthesizes the hidden backing field for you:

class Contact {
  public string Name { get; set; }
  public string Email { get; set; }
}

It stores and reads a value like a field — both through assignment and through an object initializer:

[Test]
void Auto_Property_Stores_A_Value() {
  var c = new Contact { Name = "Ada", Email = "ada@example.com" };
  Assert.Equal("Ada", c.Name);

  c.Name = "Grace";
  Assert.Equal("Grace", c.Name);
}

Asymmetric visibility — read anywhere, write only inside#

A private set narrows the write path without touching the read path: anyone can read the property, but only the class's own code can set it. It reuses the same private-member rule as a private method.

class Ledger {
  public decimal Balance { get; private set; }

  // in-class code sets it through the private setter
  public void Credit(decimal amount) {
    Balance = Balance + amount;
  }
}

From outside Ledger, ledger.Balance = 100m is a compile error — the setter is private — while ledger.Balance reads freely. A bare { get; } behaves the same way: settable inside the class, read-only to the outside.

init — settable only during construction#

An init accessor is a setter you may run only while the object is being built — in an object initializer or the constructor — and never after. It is how you make a value that is fixed once constructed:

class Booking {
  public string Reference { get; init; }
  public int Seats { get; init; }
}

Set it in the object initializer; a write afterwards is a compile error:

[Test]
void Init_Is_Set_At_Construction() {
  var b = new Booking { Reference = "BK-1", Seats = 2 };
  Assert.Equal("BK-1", b.Reference);
  Assert.Equal(2, b.Seats);
}

Writing b.Reference = "BK-2" after construction does not compile — the accessor is init-only. Reach for init when a field must be supplied when the object is made but must not change once it exists. A bodied init { … } runs its body during construction, so it can validate or transform the incoming value exactly like a set.

required — must be supplied at every new#

Marking a member required makes the compiler insist it appears in every object initializer — a missing one is a compile error, not a value silently left null:

class Registration {
  public required string Email { get; set; }
  public string? Name { get; set; }   // optional (reads back null when unset) — a bare `string Name` would itself be required
}
[Test]
void Required_Is_Supplied() {
  var r = new Registration { Email = "ada@example.com" };   // Name is optional; Email is required
  Assert.Equal("ada@example.com", r.Email);
}

Omitting Emailnew Registration { } — is a compile error. required pairs naturally with init for a value that must be given once and then frozen: public required string Email { get; init; }.

A constructor that always sets a required member takes over that obligation automatically — the compiler sees the constructor sets it, so new T(args) needs no initializer for it and no annotation:

class Membership {
  public required string Owner { get; set; }
  public Membership(string owner) { Owner = owner; }   // sets Owner on every path — the compiler infers it
}
[Test]
void Ctor_Satisfies_Required() {
  var m = new Membership("Ada");   // no `{ Owner = … }` needed — the ctor sets it
  Assert.Equal("Ada", m.Owner);
}

The inference is sound and conservative: the constructor must set the member unconditionally — directly, or through a method it calls. If it only sets the member inside an if/loop, that isn't provable, so the initializer is still required. A constructor that sets some required members can leave the rest to the caller's initializer (new Membership(owner) { OtherRequired = … }). You may still write [SetsRequiredMembers] explicitly — it is accepted and means exactly this.

It runs on the client too#

A property is a method call underneath, so it rides the same path a method does: a getter read or setter write inside a [Render(CSR)] component action runs in the browser, with no server round trip, as long as its body is client-runnable. Nothing extra is needed — the accessor ships with the component.

See also#

  • readonly fieldsreadonly fields, the field-level counterpart to { get; } and init
  • class methods — behaviour with a receiver; a property is a field-shaped pair of these
  • constructor — set up an instance's fields (and back an auto-property) at construction
  • Classes — the whole class surface: fields, constructor, methods, properties
  • type visibility (public / internal)public/private on members, and why a class defaults to internal

Related

class methods

Behaviour attached to a class — a method with a receiver, called as value.Method(). Classes are in-memory values, so a…

constructor

A class declares one constructor — its name is the class name, it takes no return type, and it runs when you write new…

Classes

A class is an in-memory shape — data plus the behaviour that belongs to it — and it never touches the database. That is…

entity

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id and audit…

type visibility (public / internal)

A top-level type carries a public or internal visibility that decides whether code outside its namespace can name it…

`readonly` fields

A `readonly` field can be assigned only where it is declared or in a constructor of the class that declares it…