Skip to content

Pattern matching

SyntaxDescriptionNote
switch (n) { case 1: … }Branch with a statement
case 1:Branch with a statement (numbers)
n switch { 1 => 10, _ => 0 }Branch with an expressionMatching an int requires a _ => case.
m switch { Mode.A => 1, _ => 0 }Branch on an enumThe _ => case can’t be omitted even when the cases are exhaustive.
s switch { "a" => 1, _ => 0 }Branch on a string
s is DotChecking the type
s is Line lChecking the type and binding
case Dot:Type in a case labelA form that branches on the type of a union value.
s switch { Dot d => …, Line l => … }Exhaustive union cases
n is 1Matching a constant
s is nullMatching against null
s is "a"Matching a string constant
m is Mode.OnMatching an enum constant
n is < 30Relational matching
n is >= 0 and <= 100Combining conditions (and pattern)
Next() is >= 0 and <= 100How many times the left side is evaluatedWriting two conditions still calls Next() only once.
n is 3 or 4Combining conditions (or pattern)
s is not nullNegation
n is int and (< 0 or > 100)Precedence with parentheses
n is var xA binding that always matches
h is { V: 1 }Matching a member valueOnly a struct or record you declared yourself can go on the left.
h is { V: > 0 }Matching a nested member value
k switch { < 30 => … }Relational case (expression)
k switch { 3 or 4 => … }Case with an or pattern (expression)
k switch { int n when n > 3 => … }Case condition (when, expression)
n switch { var v when v > 0 => … }Condition after binding
case < 30:Relational case (statement)
case 3 or 4:Case with an or pattern (statement)
case int n when n > 3:Case condition (when, statement)
SyntaxDescriptionErrorReasonAlternative
p is (1, 2)Deconstruct and match by positionTUKI0001not yetMatch by member value ({ Item1: 1 })
value is int (on a type argument’s value)Apply a pattern to a type argument’s valueTUKI0001not yetRewrite it without using a type parameter
s switch { Line l when … => … }Attach a condition to a union type patternTUKI0001not yetMove the condition into the case body, or split the case without when
n switch { 1 => 10, 2 => 20 }Omit the default case (int)TUKI0001by designAdd _ =>. It can only be omitted for a union.
m switch { Mode.A => 1, Mode.B => 2 }Omit the default case (enum)TUKI0001by designCan’t be omitted even if the cases cover every value
Forms where the default case can be omittedUnion only. For int and enums, a _ => case is required even when the cases are exhaustive
Forms where when can’t be attachedOnly the type-pattern cases of a union. Value patterns and relational patterns accept it in both switch statements and expressions
using UnityEngine;
using Tsukimi;
public class PatSwitchStatement : TsukimiBehaviour
{
void Start()
{
int n = 1;
switch (n)
{
case 1: Debug.Log(10); break; // => 10
default: Debug.Log(0); break;
}
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwStmtConst : TsukimiBehaviour
{
void Start()
{
int k = 1;
switch (k) { case 1: Debug.Log(1); break; default: Debug.Log(0); break; } // => 1
}
}

A switch expression yields the value of the case that matched.

using UnityEngine;
using Tsukimi;
public class PatDiscardArm : TsukimiBehaviour
{
void Start()
{
int n = 1;
int r = n switch { 1 => 10, _ => 0 };
Debug.Log(r); // => 10
}
}

Enum constants can be written as cases.

using UnityEngine;
using Tsukimi;
public enum Mode { A, B }
public class PatEnumConstant : TsukimiBehaviour
{
void Start()
{
Mode m = Mode.A;
int r = m switch { Mode.A => 1, _ => 0 };
Debug.Log(r); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PatStringConstant : TsukimiBehaviour
{
void Start()
{
string s = "a";
int r = s switch { "a" => 1, _ => 0 };
Debug.Log(r); // => 1
}
}

Writing a type on the right of is only works for union and interface values.

using UnityEngine;
using Tsukimi;
public abstract record Shape;
public record Dot : Shape;
public record Line(int Len) : Shape;
public class IsoIsTypeUnion : TsukimiBehaviour
{
void Start()
{
Shape s = new Dot();
bool b = s is Dot;
Debug.Log(b); // => true
}
}
using UnityEngine;
using Tsukimi;
public abstract record Shape;
public record Dot : Shape;
public record Line(int Len) : Shape;
public class PtDeclaration : TsukimiBehaviour
{
void Start()
{
Shape s = new Line(3);
if (s is Line l) { Debug.Log(l.Len); } // => 3
}
}

A type can be written in a case label too.

using UnityEngine;
using Tsukimi;
public abstract record Shape;
public record Dot : Shape;
public record Line(int Len) : Shape;
public class PtTypeCase : TsukimiBehaviour
{
void Start()
{
Shape s = new Dot();
switch (s) { case Dot: Debug.Log(0); break; default: Debug.Log(1); break; } // => 0
}
}

Writing all the union’s cases lets you omit the _ => case.

using UnityEngine;
using Tsukimi;
public abstract record Shape;
public record Dot : Shape;
public record Line(int Len) : Shape;
public class PtExhaustiveUnion : TsukimiBehaviour
{
void Start()
{
Shape s = new Dot();
int v = s switch { Dot d => 0, Line l => l.Len };
Debug.Log(v); // => 0
}
}
using UnityEngine;
using Tsukimi;
public class IsoIsConst : TsukimiBehaviour
{
void Start()
{
int k = 1;
bool b = k is 1;
Debug.Log(b); // => true
}
}
using UnityEngine;
using Tsukimi;
public class PtConstNull : TsukimiBehaviour
{
void Start()
{
string s = null;
if (s is null) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtConstString : TsukimiBehaviour
{
void Start()
{
string s = "a";
if (s is "a") { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public enum Mode { Off, On }
public class PtConstEnum : TsukimiBehaviour
{
void Start()
{
Mode m = Mode.On;
if (m is Mode.On) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtRelational : TsukimiBehaviour
{
void Start()
{
int k = 1;
if (k is < 30) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtAnd : TsukimiBehaviour
{
void Start()
{
int k = 1;
if (k is >= 0 and <= 100) { Debug.Log(1); } // => 1
}
}

The left side of is is evaluated only once.

using UnityEngine;
using Tsukimi;
public class PtAndOnce : TsukimiBehaviour
{
int Next()
{
Debug.Log(9); // => 9
return 1;
}
void Start()
{
if (Next() is >= 0 and <= 100) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtOr : TsukimiBehaviour
{
void Start()
{
int k = 3;
if (k is 3 or 4) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtNot : TsukimiBehaviour
{
void Start()
{
string s = "a";
if (s is not null) { Debug.Log(1); } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PtParen : TsukimiBehaviour
{
void Start()
{
int k = 1;
if (k is int and (< 0 or > 100)) { Debug.Log(1); }
}
}
using UnityEngine;
using Tsukimi;
public class PtVar : TsukimiBehaviour
{
void Start()
{
int k = 1;
if (k is var x) { Debug.Log(x); } // => 1
}
}

Inside { }, write the member names and the values to match them against.

using UnityEngine;
using Tsukimi;
public struct Holder { public int V { get; set; } }
public class PtProperty : TsukimiBehaviour
{
void Start()
{
Holder h = new Holder();
if (h is { V: 1 }) { Debug.Log(1); }
}
}
using UnityEngine;
using Tsukimi;
public struct Holder { public int V { get; set; } }
public class PtNested : TsukimiBehaviour
{
void Start()
{
Holder h = new Holder();
if (h is { V: > 0 }) { Debug.Log(1); }
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwExprRel : TsukimiBehaviour
{
void Start()
{
int k = 1;
int v = k switch { < 30 => 1, _ => 0 };
Debug.Log(v); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwExprOr : TsukimiBehaviour
{
void Start()
{
int k = 3;
int v = k switch { 3 or 4 => 1, _ => 0 };
Debug.Log(v); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwExprWhen : TsukimiBehaviour
{
void Start()
{
int k = 5;
int v = k switch { int n when n > 3 => n, _ => 0 };
Debug.Log(v); // => 5
}
}
using UnityEngine;
using Tsukimi;
public class R_wc : TsukimiBehaviour
{
void Start()
{
int n = 1;
int r = n switch { var v when v > 0 => 10, _ => 0 };
Debug.Log(r); // => 10
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwStmtRel : TsukimiBehaviour
{
void Start()
{
int k = 12;
switch (k) { case < 30: Debug.Log(1); break; default: Debug.Log(0); break; } // => 1
}
}
using UnityEngine;
using Tsukimi;
public class IsoSwStmtOr : TsukimiBehaviour
{
void Start()
{
int k = 4;
switch (k) { case 3 or 4: Debug.Log(1); break; default: Debug.Log(0); break; } // => 1
}
}

A case accepts the same matching forms in a switch statement and in a switch expression.

using UnityEngine;
using Tsukimi;
public class IsoSwStmtWhen : TsukimiBehaviour
{
void Start()
{
int k = 5;
switch (k) { case int n when n > 3: Debug.Log(n); break; default: Debug.Log(0); break; } // => 5
}
}