# 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.

<!-- id: ui-csharp-differences · area: ui · stability: stable · html: https://osysharp.com/reference/ui/csharp-differences/ -->

## Summary        {#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    {#description}

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

| Write this | Where |
|---|---|
| `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 array | anywhere |
| `Math.Floor(x)` · `Math.Ceiling(x)` · `Math.Round` · `Math.Abs` · `Math.Min` · `Math.Max` | anywhere |
| `p.Name?.Trim() ?? "none"` — null-conditional and null-coalescing, chained | anywhere |
| `x ??= fallback` · ternaries · `foreach` · `var` · string interpolation · LINQ | anywhere |

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

```osy title="ordinary C#, compiled" test app=ui-csharp-differences
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        {#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.

```osy syntax
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](https://osysharp.com/reference/ui/component/) for the full member list.

### `public` is a `class` modifier, not an entity one        {#public-on-a-field}

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

```osy syntax
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 { }](https://osysharp.com/reference/security/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)](https://osysharp.com/reference/types/visibility/).

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

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`:

```osy syntax
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](https://osysharp.com/reference/ui/on-change/) covers when it runs and what it depends on; [on mount / on unmount](https://osysharp.com/reference/ui/lifecycle/) covers
`on mount` / `on unmount`.

## Examples       {#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:

```osy title="all three, correct" test app=ui-csharp-differences
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       {#see-also}

[component](https://osysharp.com/reference/ui/component/) — the component archetype and every member kind it can hold.

[on change](https://osysharp.com/reference/ui/on-change/) — the reactive side-effect block, and what makes it re-run.

[on mount / on unmount](https://osysharp.com/reference/ui/lifecycle/) — `on mount` and `on unmount`.

[type visibility (public / internal)](https://osysharp.com/reference/types/visibility/) — visibility on TYPES (where entity and class defaults genuinely differ).

[class methods](https://osysharp.com/reference/class/methods/) — methods on a `class`, which follow the same shape as a component's.
