Skip to content

Control flow

SyntaxDescription
if / elseBranch on a condition
switch (n) { case 1: ... }Branch on a value (statement)
n switch { 1 => 10, _ => 0 }Branch on a value (expression)
n switch { var v when v > 0 => 10, _ => 0 }Case with a condition
n > 0 ? 10 : 20Conditional operator
while (n < 3)Loop while a condition holds
do { ... } while (n < 3);Loop with the check after the body
for (int i = 0; i < 3; i++)Loop a fixed number of times
for (int i = 0, j = 3; i < j; i++, j--)Loop with multiple variables
foreach (int v in a)Iterate over an array
foreach (var (a, b) in ps)Deconstruct while iterating
for { for { } }Nested loops
breakExit a loop
continueContinue to the next iteration
while (true) { ... break; }Exit an infinite loop
if (...) { return; }Return partway through
{ … }Block
;Empty statement
const int a = 1;Local constant
int Twice(int x) { return x * 2; }Local function
static int Twice(int x) { … }static local function
for (;;)Omitting parts of a for
return;Return without a value
SyntaxDescriptionErrorReasonInstead
goto top;Jump to a labelTUKI0001, TUKI0099by designRewrite with a loop
goto case c; / goto default;Jump to another switch caseTUKI0001by designMove the shared logic into a method and call it from both case blocks
L: stmtPut a label on a statementTUKI0099by designThere’s no goto, so rewrite without a label
throw new Exception();Throw an exceptionTUKI0001, TUKI0101runtimeRepresent failure with a return value or state
try { } catch { }Catch an exceptionTUKI0001runtimeCheck the condition beforehand
catch (…) when (…)Catch an exception conditionallyTUKI0001runtimeSame as above
try { } finally { }Run cleanup whether or not an exception occurredTUKI0001runtimeWrite the cleanup at the end of the block
using (r) { … }Clean up when leaving a scopeTUKI0001not yetCall the cleanup method yourself (there’s no finally, so it won’t run if you leave early)
using var r = …;Let the declared variable’s cleanup happen automaticallyTUKI0001, TUKI0099not yetSame as above
yield return / yield breakReturn values one at a timeTUKI0001, TUKI0099runtimePack them into an array and return it
p switch { (1, 2) => … }Deconstruct by position in a caseTUKI0001not yetWrite { Item1: 1, Item2: 2 }, or read the elements and compare them
void Bump() { n = n + 1; } (writes back to an outer variable)Rewrite an outer variable from a function inside a methodTUKI0001undecidedReturn it as a value and let the caller assign it (a function that only reads the value can be written)
ref int r = ref a;Give a variable an aliasTUKI0001undecidedAssign the value and use it
lock (o) { … }Do mutual exclusionTUKI0001runtimeThere’s only one thread of execution, so it isn’t needed
async Task Go() { await ...; }Await asynchronouslyTUKI0001, TUKI0101runtimeUse a mechanism that calls back after a delay
n switch { 1 => 10, 2 => 20 }Branch without a default caseTUKI0001by designAlways include _ =>. It can only be omitted for a union
foreach (var (a, b) in ps) (a type with a hand-written Deconstruct)Use your own hand-written deconstructionTUKI0001by designRead the fields by name
using UnityEngine;
using Tsukimi;
public class ExpressionsIfElse : TsukimiBehaviour
{
void Start()
{
int n = 1;
if (n > 0) { n = 2; } else { n = 3; }
Debug.Log(n); // => 2
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsSwitchStatement : TsukimiBehaviour
{
void Start()
{
int n = 1;
switch (n)
{
case 1: n = 10; break;
default: n = 0; break;
}
Debug.Log(n); // => 10
}
}

A _ => case is required. Even if the cases cover every value, it can’t be omitted for int or an enum. It can only be omitted for a union.

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

Writing when selects the case only when the pattern matches and the condition is true. It can’t be written on a case in a switch statement.

using UnityEngine;
using Tsukimi;
public class ExpressionsSwitchArmWhen : 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 ExpressionsTernary : TsukimiBehaviour
{
void Start()
{
int n = 1;
int r = n > 0 ? 10 : 20;
Debug.Log(r); // => 10
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsWhile : TsukimiBehaviour
{
void Start()
{
int n = 0;
while (n < 3) { n++; }
Debug.Log(n); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsDoWhile : TsukimiBehaviour
{
void Start()
{
int n = 0;
do { n++; } while (n < 3);
Debug.Log(n); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsFor : TsukimiBehaviour
{
void Start()
{
int total = 0;
for (int i = 0; i < 3; i++) { total += i; }
Debug.Log(total); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsForMultiInit : TsukimiBehaviour
{
void Start()
{
int count = 0;
for (int i = 0, j = 3; i < j; i++, j--) { count++; }
Debug.Log(count); // => 2
}
}

foreach can only iterate over an array. The runtime has no enumerator.

using UnityEngine;
using Tsukimi;
public class ExpressionsForeachArray : TsukimiBehaviour
{
void Start()
{
int[] a = new int[] { 1, 2 };
int total = 0;
foreach (int v in a) { total += v; }
Debug.Log(total); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsForeachDeconstruct : TsukimiBehaviour
{
void Start()
{
(int, int)[] ps = { (1, 2) };
foreach (var (a, b) in ps) { Debug.Log(a + b); } // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNestedLoop : TsukimiBehaviour
{
void Start()
{
int count = 0;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++) { count++; }
}
Debug.Log(count); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsBreak : TsukimiBehaviour
{
void Start()
{
int count = 0;
for (int i = 0; i < 5; i++)
{
if (i == 2) { break; }
count++;
}
Debug.Log(count); // => 2
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsContinue : TsukimiBehaviour
{
void Start()
{
int count = 0;
for (int i = 0; i < 5; i++)
{
if (i == 2) { continue; }
count++;
}
Debug.Log(count); // => 4
}
}

while (true) keeps running without evaluating a condition. Exiting it needs a break in the body.

using UnityEngine;
using Tsukimi;
public class ExpressionsWhileTrueBreak : TsukimiBehaviour
{
void Start()
{
int n = 0;
while (true)
{
n++;
if (n > 2) { break; }
}
Debug.Log(n); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsReturnEarly : TsukimiBehaviour
{
private int Level() { return 1; }
void Start()
{
Debug.Log(1);
if (Level() > 0) { return; }
Debug.Log(2);
}
}
// Output
// 1
using UnityEngine;
using Tsukimi;
public class StBlock : TsukimiBehaviour
{
void Start()
{
{
int a = 1;
Debug.Log(a); // => 1
}
}
}
using UnityEngine;
using Tsukimi;
public class StEmpty : TsukimiBehaviour
{
void Start()
{
;
Debug.Log(1); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class StLocalConst : TsukimiBehaviour
{
void Start()
{
const int a = 1;
Debug.Log(a); // => 1
}
}

A function can be declared inside a method. It can read outer variables, but can’t write back to them (Forms that don’t compile).

using UnityEngine;
using Tsukimi;
public class StLocalFunc : TsukimiBehaviour
{
void Start()
{
int Twice(int x) { return x * 2; }
Debug.Log(Twice(2)); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class StLocalFuncStatic : TsukimiBehaviour
{
void Start()
{
static int Twice(int x) { return x * 2; }
Debug.Log(Twice(2)); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class StForOmitted : TsukimiBehaviour
{
void Start()
{
int i = 0;
for (;;) { i++; if (i > 2) break; }
Debug.Log(i); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class StReturnVoid : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
return;
}
}