Statements are what a method body is made of (C# specification clause 13). The
try statement is on Exceptions, the
patterns used by switch are on Patterns,
and yield on Async
and iterators.
Kinds of statement
| Statement | Form | Clause |
|---|---|---|
| Block | { … } | 13.3 |
| Empty | ; | 13.4 |
| Labelled | name: statement | 13.5 |
| Declaration | int x = 1;, const int N = 1;, a local function | 13.6 |
| Expression | x = 1;, f();, i++; | 13.7 |
| Selection | if, switch | 13.8 |
| Iteration | while, do, for, foreach | 13.9 |
| Jump | break, continue, goto, return, throw | 13.10 |
| Try | try | 13.11 |
| Checked | checked, unchecked | 13.12 |
| Lock | lock | 13.13 |
| Using | using | 13.14 |
| Yield | yield | 13.15 |
| Fixed | fixed | 24.7 |
| Unsafe | unsafe { … } | 24.2 |
An expression statement must be an assignment, a call, an increment or decrement, an
await, or a new expression. Forms like x + 1; are
errors. A statement or block that the compiler can prove is never reached gives a
warning (clause 13.2). A non-void method whose end can be reached is an error.
if
if (x > 0)
Console.WriteLine("positive");
else if (x < 0)
Console.WriteLine("negative");
else
Console.WriteLine("zero");
The condition must be bool or convert to it. There is no conversion
from integers. An else belongs to the nearest if.
switch
switch (shape)
{
case Circle c when c.Radius > 10:
Console.WriteLine("big circle");
break;
case Circle:
case Square: // shared code for two labels
Console.WriteLine("small");
break;
case null:
break;
default:
throw new ArgumentException(nameof(shape));
}
- The expression may be of any type (clause 13.8.3). Each
caselabel is a pattern (C# 7.0), or a constant, with an optionalwhenguard. - No fall-through. The end of a section must not be reachable. Each
section ends in
break,return,throw,continue, orgoto case/goto default. Two labels directly together share one section. - A label is chosen in order, top to bottom, among those whose pattern and guard match.
The
defaultsection runs only if no other label matches, wherever it is written. - A
goto case 1;jumps to another section. - The switch expression (C# 8) is a separate form that produces a value.
while
while (condition) statement tests the condition first, and runs the
statement while it is true.
do
do statement while (condition); runs the statement once, and then
repeats while the condition is true.
for
for ( [initializer] ; [condition] ; [iterator] ) statement
for (int i = 0, j = 10; i < j; i++, j--)
Console.WriteLine(i + j);
for (;;) { break; } // all three parts may be empty
The initialiser is a local declaration or a list of statement expressions, and its variables are in scope through the loop. The iterator is a list of statement expressions. The whole statement is a new scope.
foreach
foreach ( [ref [readonly]] type identifier in expression ) statement
foreach ( type deconstruction in expression ) statement
await foreach ( … ) statement
foreach (var item in list) Console.WriteLine(item);
foreach (var (key, value) in dict) { } // deconstruction
foreach (ref int x in span) x++; // ref loop variable
await foreach (var row in ReadAsync()) { } // see Async and iterators
- The collection needs a
GetEnumeratormethod returning a type withMoveNextandCurrent, either directly or throughIEnumerableorIEnumerable<T>(clause 13.9.5). It need not implement the interface. Since C# 9 an extensionGetEnumeratorworks too. - The loop variable is read-only unless declared with
ref. It is a new variable in each iteration (since C# 5), so a lambda capturing it sees that iteration’s value. - An enumerator that is disposable is disposed after the loop, even if the loop ends early.
- Changing a
List<T>during its loop throwsInvalidOperationExceptionat the nextMoveNext. - A cast to the declared type is applied to each element, implicitly.
Jump statements
| Statement | Effect |
|---|---|
break; | Ends the nearest loop or switch |
continue; | Starts the next iteration of the nearest loop |
goto label; | Jumps to a label in the same or an enclosing block |
goto case 1;, goto default; | Jumps to a section of the enclosing switch |
return;, return x;, return ref x; | Leaves the function |
throw x;, throw; | Throws; see Exceptions |
A goto cannot jump into a block or out of a finally block.
Control cannot leave a finally block with return,
break or continue.
Labelled break and continue
Since C# 15, break and continue can name a label placed on
an enclosing loop or, for break, an enclosing switch
(feature specification Labeled break and continue). Without a label they
behave as above.
outer: for (int row = 0; row < rows; row++)
{
for (int column = 0; column < columns; column++)
{
if (grid[row, column].IsBlocked) continue outer;
if (grid[row, column].IsGoal) break outer;
}
}
checked and unchecked
In a checked context, an integer overflow throws
OverflowException. In an unchecked context, the high bits are
dropped (clause 13.12). The default is unchecked, unless the
CheckForOverflowUnderflow project option is on.
int a = int.MaxValue;
int b = checked(a + 1); // OverflowException
int c = unchecked(a + 1); // int.MinValue
checked
{
int d = a + 1; // OverflowException
}
The context affects ++, --, unary -,
+, -, *, / and explicit numeric
conversions between integers. Constant expressions are checked at compile time unless
they are in an unchecked context. Since C# 11 a type can provide
checked operators. A context
applies to the code written inside it, not to methods it calls.
lock
private readonly object gate = new();
lock (gate)
{
count++;
}
lock (x) takes a mutual-exclusion lock for the duration of the statement
(clause 13.13). The expression must be of a reference type. It compiles to a
try/finally around Monitor.Enter and
Monitor.Exit, so the lock is released if an exception is thrown. An
await cannot be used inside a lock.
Since C# 13, if the target is a System.Threading.Lock, the compiler instead
uses Lock.EnterScope(), whose result is a ref struct that
ends the scope when disposed.
using
A using statement guarantees that a resource is disposed (clause 13.14).
This is unrelated to the using
directive.
using (var f = File.OpenRead(path)) // statement form
{
Read(f);
}
using var g = File.OpenRead(path); // declaration form, C# 8: disposed at end of block
await using var h = new AsyncThing(); // IAsyncDisposable
- The resource must implement
IDisposable, or be aref structwith a suitableDisposemethod (C# 8). - It compiles to a
try/finallythat callsDispose. A null resource is allowed and is skipped. - The variable is read-only inside the statement.
- A
usingdeclaration disposes at the end of the enclosing block, in the reverse order of declaration. It cannot be in aswitchsection without a block. await usingusesIAsyncDisposable.DisposeAsync(C# 8).
Other statements
A yield statement is part of an
iterator. A
fixed statement pins memory in unsafe
code. Local functions are on Classes.