# 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 [Type(string)] — which is what you want when a human or another system will read the column.

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

## Summary        {#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`](https://osysharp.com/reference/function/switch/) over it can be checked for completeness.

## Signature      {#signature}
```osy syntax
enum <Name> { <Member>, <Member>, … }

[Type(string)]                       // persist the member's NAME instead of a number
enum <Name> { <Member>, … }
```

## Description    {#description}

### How do I declare one and set it on a row?   {#using}
An enum is declared at the top level and used as a member type:

```osy title="a status enum" test app=enum-declaration
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?   {#storage}
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:

```osy title="an enum whose values are readable in the database" test app=enum-declaration
[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`   {#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.

```osy syntax
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 [Osysharp.Ui (the UI kit)](https://osysharp.com/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](https://osysharp.com/reference/enum/labels/).

`.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](https://osysharp.com/reference/enum/labels/)).
A generic component can take the same array as a default: `component Picker<T>(Binding<T> value, T[] rows = T.Members)`
— see [generic component](https://osysharp.com/reference/ui/generic-component/).

### How do I add an "All" option to an enum filter?   {#all-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 `||`:

```osy title="a filter bar with an All option" test app=enum-all-filter
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?   {#closed}
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](https://osysharp.com/reference/entity/declaration/) with rows.

## See also       {#see-also}
- [[Label], [Icon], [Tone] — what a human reads](https://osysharp.com/reference/enum/labels/) — `[Label]`, and what a screen shows for an enum value
- [generic component](https://osysharp.com/reference/ui/generic-component/) — `T[] rows = T.Members`, one component over any enum
- [entity members](https://osysharp.com/reference/entity/properties/) — using an enum as a member type
- [switch](https://osysharp.com/reference/function/switch/) — branching over every case of an enum
- [entity](https://osysharp.com/reference/entity/declaration/) — what to use instead when the values are data, not code
