Summary#
An enum is a fixed set of named values — Draft, Placed, Shipped. Use one wherever a member has a known,
closed set of states. The compiler then knows every possible value, so a typo is an error and a
switch over it can be checked for completeness.
Signature#
enum <Name> { <Member>, <Member>, … }
[Type(string)] // persist the member's NAME instead of a number
enum <Name> { <Member>, … }Description#
How do I declare one and set it on a row?#
An enum is declared at the top level and used as a member type:
enum OrderStatus { Draft, Placed, Shipped, Cancelled }
entity Order {
[Required] string Code;
OrderStatus Status;
}
void Place(string code) {
var o = Order.Single(x => x.Code == code);
o.Status = OrderStatus.Placed; // always qualified — OrderStatus.Placed, never "Placed"
}
int PlacedCount() {
return Order.Where(o => o.Status == OrderStatus.Placed).ToList().Count;
}You always write the member qualified — OrderStatus.Placed — so a misspelling is a compile error rather than a
string that silently matches nothing.
Is it stored as a number or as its name?#
By default an enum member is stored as a number: Draft is 0, Placed is 1. That is compact, and it has a sharp
edge — the number means nothing on its own. Anyone reading the table directly, exporting it, or pointing a reporting
tool at it sees 1 and has to come back to the source to learn what that means. Worse, reordering the members
silently changes what the stored rows mean.
The [Type] attribute chooses the storage. [Type(string)] stores the member's name instead of a number:
[Type(string)]
enum Priority { Low, Normal, Rush }
entity Ticket {
[Required] string Title;
Priority Priority;
}Now the column holds "Rush". It costs a few bytes per row and buys you a table that explains itself, an export that
a human can read, and the freedom to reorder the members without rewriting history.
Reach for [Type(string)] whenever anything outside the app will read the column — a report, an export, an
integration, a support engineer at 3am. Keep the default numeric form for values that are purely internal.
How do I loop over every member? — .Members#
TheEnum.Members is every member of an enum, in declaration order — the array a picker, a filter bar or a set of
tabs is built from. It reads the type, so it can never drift from it: add a member and every list built this way
grows with it.
Its element IS the enum value — Room.Members is a Room[], not a list of anything wrapping one. So r below
compares with == against a Room field, passes to anything taking a Room, and r.Label is a read on the value
itself. Enum.GetValues<Room>() is the C# spelling of the same array, and compiles too.
enum Room { [Label("Living room")] LivingRoom, Kitchen } // NO [Label] → .Label IS the bare name: "LivingRoom"
// WHERE THE LABEL APPEARS BY ITSELF, and where it does not — the difference is the SLOT, not the value.
Text(kiln.Room); // a text slot reads the [Label] label — nothing to write
Badge(kiln.Room.Label, tone: t); // a `string` PARAMETER takes a string, so say .Label
Text("in " + kiln.Room); // ⚠ a CONCAT is a string operation: "in LivingRoom", the raw member name.
Row { Text("in "); Text(kiln.Room); } // …so put the enum in its own text slot when you want the label
// A MEMBER MAY SHARE ITS TYPE'S NAME, exactly as in C#. `Room Room;` is fine and needs no second word.
entity Kiln { Room Room; }
foreach (var r in Room.Members) { Tab(r.Label, selected: r == room, onPress: () => Show(r)); }
Dropdown("Room", value: kiln.Room) // a dropdown over an enum needs no options at all — see [Osyrin.Ui (the UI kit)](/reference/ui/kit/)The label trap, in one table — the same value reads two different ways depending on the SLOT it lands in:
| you write | you get | why |
|---|---|---|
Text(p.Room) | Living room — the label | a text slot RENDERS an enum value, so [Label] applies |
Text("in " + p.Room) | in LivingRoom — the member name | ⚠ + is a string op, and an enum's string form is its name (C#-exact). It compiles; it just shows the wrong words |
Text("in " + p.Room.Label) | in Living room | .Label is the read that hands you the label as a string |
Badge(p.Room.Label, tone: t) | Living room | a string PARAMETER takes a string, so say .Label |
.Label is a property, not a method — r.Label, never r.Label(). Full story: [Label], [Icon], [Tone] — what a human reads.
.Label on a member is what a person reads (the [Label] text, or the member's own name — see [Label], [Icon], [Tone] — what a human reads).
A generic component can take the same array as a default: component Picker<T>(Binding<T> value, T[] rows = T.Members)
— see generic component.
How do I add an "All" option to an enum filter?#
An enum is a closed set, so "no filter" is not one of its members — it is the absence of a choice. Hold the
choice in a T? and let null mean all; the extra tab is then an ordinary tab whose selected: asks whether
anything is chosen, and the filter is one ||:
enum Room { Kitchen, Bathroom, Bedroom }
entity Kiln {
[Required, MaxLength(80)] string Name;
[Required] Room Where;
security { allow read, create when IsAnonymous || IsAuthenticated; }
}
[Page("/kilns")]
[AllowAnonymous]
[Render(CSR)]
[Title("Kilns")]
component Kilns() {
live var kilns = Kiln.OrderBy(p => p.Name);
Room? only = null; // null IS "All" — the absence of a choice
action Show(Room r) { only = r; }
action ShowAll() { only = null; }
render {
Tabs {
Tab("All", selected: only == null, onPress: ShowAll);
foreach (var r in Room.Members) { Tab(r.Label, selected: only == r, onPress: () => Show(r)); }
}
foreach (var p in kilns) {
if (only == null || p.Where == only) { Text(p.Name); }
}
}
}The values come from data — should this be an enum?#
The set is fixed at compile time. If the values come from data — a list of categories an administrator maintains — that is not an enum; it is an entity with rows.
See also#
- [Label], [Icon], [Tone] — what a human reads —
[Label], and what a screen shows for an enum value - generic component —
T[] rows = T.Members, one component over any enum - entity members — using an enum as a member type
- switch — branching over every case of an enum
- entity — what to use instead when the values are data, not code