# List indexer

> Positional get/set on a List<T> by integer index, exactly like C#'s List<T>.this[int]. The index must be an integer; the result is the element type. Out-of-range access faults at runtime, as in C# — use `ElementAtOrDefault(i)` when the element may legitimately not be there.

<!-- id: function-list-indexer · area: function · stability: stable · html: https://osysharp.com/reference/function/list-indexer/ -->

## Summary        {#summary}
`list[i]` reads, and `list[i] = v` writes, the element at integer position `i` of a `List<T>` — exactly
C#'s `List<T>.this[int]`. The read yields the element type `T`; the write assigns `v` in place. As in C#,
an out-of-range index **faults at runtime** (there is no silent clamp).

## Signature      {#signature}
```osy syntax
list[<int> i]           // read  → T
list[<int> i] = <T> v   // write (in place)
```

## Description    {#description}
The receiver must be a `List<T>` (the mutable list — `new List<T>()` or a `Text.Split` result). The index
expression must be an integer; a non-integer index is a compile error
(`list index must be an integer, got '…'`). Indexing a value that is not indexable is likewise a compile
error.

This complements the collection surface a `List<T>` already exposes — `foreach`, `.Count`, `.Add`,
`.Contains`, `.Remove`. `Dictionary<K,V>` uses the same `[…]` syntax keyed by `K`; a `HashSet<T>` has **no**
indexer (as in C#).

### When the element may not be there — `ElementAtOrDefault(i)`    {#element-at-or-default}
`list[i]` faults past the end, which is right when a missing element means a bug. When it does **not** — reading the
third segment of a path that may only have two — ask for it directly:

```osy title="a segment that may not be there" test app=list-indexer
string SlugOf(string path) {
  var parts = Text.Split(path, "/");        // "/org/acme" -> ["", "org", "acme"]
  return parts.ElementAtOrDefault(2) ?? "";  // "" when the path is shorter
}
```

C#'s own spelling, with C#'s own semantics: the miss yields **null** rather than faulting, and a negative index is a
miss too (never "counting from the end"). The result is **nullable**, so `??` reads naturally and the compiler makes
you say what the miss means.

Prefer it over testing the shape of the input first. A guard like `path.StartsWith("/org/") ? parts[2] : ""` answers a
*different* question than "is there a third segment", and the two drift apart the moment the input shape changes —
whereas guards that carry real meaning (only an `/org/` path *has* a slug) are worth keeping, and this does not replace
them.

## Examples       {#examples}
```osy title="get, set, computed index" test app=list-indexer
string Reorder(string csv) {
  var xs = Text.Split(csv, ",");   // a List<string>
  var first = xs[0];               // read
  xs[0] = xs[xs.Count - 1];        // write, computed index
  xs[xs.Count - 1] = first;        // swap first and last
  return string.Join(",", xs);
}
// Reorder("a,b,c")  ->  "c,b,a"
```

## See also       {#see-also}
- [Text.Split](https://osysharp.com/reference/function/text-split/) — produces a `List<string>` this indexes
- [LINQ over a local list](https://osysharp.com/reference/query/in-memory-linq/) — the LINQ verbs over a list you already hold
