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

Reference / UI

Writing a component — what differs from C#

Osy# is C# almost everywhere, which is what makes the handful of deliberate differences worth knowing before you hit them. Ordinary C# works and should be written directly — switch expressions, `Math.Floor`/`Math.Ceiling`, `Room.Members.Length`, and `p.Name?.Trim() ?? "none"` all compile, in a function and in a `render` block alike. Inside a component three things differ: there is no `method` keyword (a method is a return type and a name, exactly as in C#), `public` is a `class` modifier and not an entity one, and a reactive side effect is `on change { … }` rather than a named `effect`. Each of these produces a clear compile error — this page is so you meet them here first.

stable2 examples compiled by CIuiauthoringguide

Summary#

Osy# is C#, and the goal is that your C# instincts are right. They almost always are — which is exactly why the few places they are not cost more than their number suggests: you write the C# form, and only a failed compile tells you otherwise.

Three of those live in and around a component. All three produce a clear error naming the replacement, so nothing here is a trap you can ship — but reading them once is cheaper than meeting them one failed compile at a time.

Description#

Ordinary C# that works — write it, do not route around it#

Write thisWhere
r switch { Room.Kitchen => 1, Room.Bedroom => 2, Room.Bath => 3 }a function, a computed, a render block
Room.Members.Length — and Enum.GetValues<Room>() is the same arrayanywhere
Math.Floor(x) · Math.Ceiling(x) · Math.Round · Math.Abs · Math.Min · Math.Maxanywhere
p.Name?.Trim() ?? "none" — null-conditional and null-coalescing, chainedanywhere
x ??= fallback · ternaries · foreach · var · string interpolation · LINQanywhere

Write the C# form first. A construct Osy# does not take is a compile error naming the line and the replacement.

enum Room { Kitchen, Bedroom, Bath }

entity Kiln {
  [MaxLength(80)] string Name = "";
  Room Where = Room.Kitchen;
  decimal Litres = 0m;
  security { allow read, create, update, delete when IsAnonymous || IsAuthenticated; }
}

int Rank(Room r) => r switch { Room.Kitchen => 1, Room.Bedroom => 2, Room.Bath => 3 };

int RoomCount() { return Room.Members.Length; }

decimal WholeLitres(Kiln p) { return Math.Floor(p.Litres); }

string Caption(Kiln p) { return p.Name?.Trim() ?? "unnamed"; }

There is no method keyword#

A component method is a return type and a name, exactly as in C#. The token after the name is what distinguishes a method from a field.

component ReportRow(Report report) {
  string Load() { … }        // ✓ a method — return type, name, parameter list
  method string Load() { … } // ✗ `method` is not a keyword
}

method is the word most people try, because the surrounding declarations (action, on change) do read as keywords. They are different things: action and on change name reactive machinery that has no C# equivalent, so they get a word. A method is just a method, so it looks like one.

See component for the full member list.

public is a class modifier, not an entity one#

This is the difference most likely to bite, because the two forms sit a line apart and look alike:

class ReceiptFields {
  public decimal? Amount;   // ✓ a class member IS private by default — exactly C#
}

entity Report {
  public string Title;      // ✗ refused
  string Title;             // ✓
}

A class is an ordinary C# type: its members are private by default and public opens them. An entity is not — its fields are a data surface, and who may read or write them is not a property of the field but a declared rule on the entity (security { }). Access control there is the security { } block, and allowing public on a field would suggest a second, weaker answer to a question that already has one.

The compiler says so directly:

Visibility/const modifiers apply to class members; entity access control is the security { } block.

Note this is about the FIELD. Entity and class types do take a visibility modifier, and their defaults differ — see type visibility (public / internal).

A reactive side effect is on change { … }, not a named effect#

There is no effect keyword. A block that reacts to its dependencies changing is on change, one of the lifecycle family alongside on mount and on unmount:

component Search(string term) {
  on change { … }           // ✓ runs when what it reads changes
  effect Watch { … }        // ✗ `effect` is not a keyword
}

Writing the old form gets an error that names the replacement and the rest of the family, so you land in the right place from the first attempt. on change covers when it runs and what it depends on; on mount / on unmount covers on mount / on unmount.

Examples#

All three correct at once — a method declared C#-style, a class whose members take public, and an entity whose fields do not. This one is compiled by the documentation build, so it is the shape to copy:

class Filters {
  public string? Term;               // class member: private by default, `public` opens it
}

entity Report {
  [MaxLength(200)] string Title = "";   // entity field: no visibility modifier
}

[Page("/reports")]
[AllowAnonymous]
component ReportList() {
  var filters = new Filters();

  string Caption() {                 // a method — a return type and a name, no `method` keyword
    return filters.Term == null ? "All reports" : "Filtered";
  }

  render { Text(Caption()); }
}

See also#

component — the component archetype and every member kind it can hold.

on change — the reactive side-effect block, and what makes it re-run.

on mount / on unmounton mount and on unmount.

type visibility (public / internal) — visibility on TYPES (where entity and class defaults genuinely differ).

class methods — methods on a class, which follow the same shape as a component's.

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

on mount / on unmount

`on mount { … }` runs a block ONCE, the first time a component appears — before its first paint; `on unmount { … }`…

on change

`on change { … }` is a reactive **side-effect**: the runtime re-runs it whenever a value it read changes, so it's how…

The reactivity & lifecycle model

How an Osy# component comes alive and stays in sync: declarations are live value bindings, `on mount`/`on unmount` are…

type visibility (public / internal)

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

class methods

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

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

switch expression

Choose a VALUE by matching a subject against patterns. Arms are tried in order and the first match wins. Patterns are a…

Math.Abs, Math.Sign, Math.Min, Math.Max, Math.Clamp, Math.Truncate, Math.Pow, Math.Sqrt, Math.Sin, Math.Cos, Math.Tan

The numeric helpers, each returning the type C# says it returns. Abs, Min, Max and Clamp answer in the WIDEST of their…

enum

A fixed set of named values, used as a member type. Stored as a number by default, or as the member's own name with…

Compound assignment (+= -= *= /= %= ??=)

Update a variable or property in place: x op= y is shorthand for x = x op y. Arithmetic forms need a numeric lvalue…