Summary#
Whether a member is required or optional is carried by its type spelling — there is no separate keyword to
remember. A bare member is non-nullable; the ? suffix makes it optional (it reads back null). What "bare" means
depends on whether the type has a natural zero:
- A value type with a natural zero —
int,long,double,decimal,bool,TimeSpan— reads that zero when left unset.int Count;reads back0, exactly as a C# field would. - A value-shaped type with no natural zero —
string, anyenum,DateTime/DateOnly/TimeOnly,Guid,Json— is required: the platform invents no value for it, so you must supply one before the row is saved. - An entity reference is the exception: a bare reference is optional. Write
[Required]to demand it. - A
Markdownmember is also optional, for a different reason: it holds a document stored as its own sections, so a document nobody has written simply is not there and there is nothing to supply. See Markdown.
Signature#
entity Ticket {
string Title; // REQUIRED — a bare string has no natural zero, so you must set it
string? Note; // optional — reads back null when unset
int Priority; // reads back 0 (int has a natural zero)
Status State; // REQUIRED — an enum has no natural zero
Status State2 = Status.Open; // optional-with-a-default — the default supplies the value
DateTime? ResolvedAt; // optional — null until it is resolved
Customer Reporter; // OPTIONAL — references are optional by default
[Required] Team Team; // required — opt a reference in with [Required]
}Description#
Three ways to spell a member#
Every member falls into one of three shapes:
- Bare (
string Title;) — non-nullable. For a natural-zero type this reads the zero; for everything else it is required. - Optional (
string? Note;) — nullable. Reads backnullwhen nobody set it. - Defaulted (
Status State = Status.Open;) — non-nullable, but you supplied the value once at the declaration, so the row is never without one.
Why a bare string is required#
In C#, default(string) is null, not "". An empty string is a value someone typed, not the absence of one —
conflating them hides bugs. So Osy# does not invent an empty string for you: a bare string Title; must be given a
value by the time the row is saved. If you genuinely want "maybe unset," say so with string? Title;, which reads back
null. The same reasoning covers DateTime, Guid, and Json — there is no honest "zero" of those types to fall
back on.
Enums are required, not silently first#
A bare enum is required. It is not quietly defaulted to whichever member you happened to list first —
reordering the members would silently change the stored default, and a value at position zero may not even be a member
you named. Give it an explicit default when you want one (Status State = Status.Open;), make it optional
(Status? State;), or supply it before the row is saved.
References are optional by default#
An entity reference is the deliberate exception. The everyday shape is: create the row, then let the user pick the
related record from a dropdown — so demanding the reference at creation would be wrong. A bare Customer Reporter; is
therefore optional (reads back null). When a reference truly must be present, mark it [Required] Team Team;.
When a required member is checked#
"Required" means present when the value becomes real — and that moment depends on what you are building:
Entities are saved, so a required entity member is checked when you commit the row. This is what makes the everyday create-form work: seed an empty draft (
draft = new Ticket {}), bind each field to an input, and let the user fill it — the required members are checked when the row is saved, not while it is still being typed. Commit a row with a required member still unset and it is refused, naming the member.A
classis a transient value with no save step, so its required members are checked at construction — the compiler stops you atnew, naming the member and the three fixes:new Receipt { } // error: 'Receipt.Number' must be given a value — set it here // (new Receipt { Number = … }), give it a default (Number = …;), // or make it optional (string? Number;).
Examples#
A bare string parameter is required; the ? suffix makes one optional and it reads back null — the same spelling
rule that governs entity members, shown here on ordinary values so it compiles on its own.
string DisplayName(string name, string? nickname) {
// `name` is required (a bare string); `nickname` is optional (`?`), so it may be null.
return nickname == null ? name : name + " (" + nickname + ")";
}
// DisplayName("Ada", null) -> "Ada"
// DisplayName("Ada", "Countess") -> "Ada (Countess)"