Summary#
subject switch { … } is an EXPRESSION: it produces a value. Arms are tried top to bottom and the first
one that matches wins. It is the form to reach for wherever a value is wanted rather than a statement —
notably inside a render block, which takes no statements.
Over a closed vocabulary (an enum, or a bool) a switch expression with no _ arm must cover every
member, and a gap is a compile error.
Signature#
subject switch {
Member => value, // a CONSTANT — an enum member or a literal
>= 1000.0 => value, // a RELATIONAL comparison: < <= > >=
Circle c => value, // a TYPE pattern, with a required binding
_ => value, // the DISCARD — matches anything
}Description#
Arms are tried in order, and order is the whole design of a relational ladder. 1500000 satisfies both
>= 1000000 and >= 1000; writing the larger bound first is what makes it answer "M".
A relational arm compares against a constant and may use any of <, <=, >, >=. There is no ==
form because a bare constant arm already means equals.
Exhaustiveness is checked only where a vocabulary is closed. An enum and a bool have a knowable set
of values, so every one must have an arm or the compile fails. A number does not, so a switch over one
always needs _ — including a relational ladder, because proving that < 0 and >= 0 between them cover
every number is arithmetic the compiler does not attempt. It asks for a _ you may not strictly need
rather than claiming a gap is covered when it is not.
A missing enum arm is an ERROR, not a warning. This is a deliberate divergence from C#, which warns at compile time and throws at run time. A throw inside a render expression is a blank page instead of a message, so the check that prevents it has to be the one that cannot be ignored.
The subject is read once per arm test, so it must be re-readable at no cost — a name, a member access,
an index. Anything that could do work (a call, an await) is refused, and the refusal names the one-line
fix: assign it to a local first.
A when guard is not part of this form. Use an if for that.
Examples#
public enum MarkKind { Line, Area, Column }
// RELATIONAL — the compact-number ladder. The larger bound comes first because the first match wins.
string Compact(int n) {
return n switch { >= 1000000 => "M", >= 1000 => "K", _ => "" };
}
// All four ordering operators.
string Band(int n) {
return n switch { < 0 => "neg", <= 10 => "small", > 100 => "big", _ => "mid" };
}
// A CONSTANT arm over an enum. No `_`: every member has an arm, which is what makes adding a member to
// the vocabulary a build failure at each site that must decide about it.
string Family(MarkKind k) {
return k switch { Line => "stroke", Area => "stroke", Column => "fill" };
}
// A bool is a closed vocabulary too, so both cases are exhaustive with no `_`.
string YesNo(int n) { return (n > 0) switch { true => "yes", false => "no" }; }class Shape { public string Name; }
class Circle : Shape { public double Radius; }
// `Circle c` binds `c` at the narrower type, so `c.Radius` reads inside that arm.
string Describe(Shape s) {
return s switch { Circle c => $"circle r={c.Radius}", _ => s.Name };
}See also#
- switch — the switch STATEMENT, which branches control rather than producing a value
- if / else — the two-way conditional, and where a
when-guard-shaped test belongs - Testing which class a value is —
is/as/ a cast /OfType<T>(), the other places a type is asked about - Enums — why a closed vocabulary is what makes exhaustiveness checkable