C# provides a variety of statements. Most of these statements will be familiar to developers who have programmed in C and C++.
statement:
labeled-statement
declaration-statement
embedded-statement
embedded-statement:
block empty-statement
expression-statement
selection-statement
iteration-statement
jump-statement
try-statement
checked-statement
unchecked-statement
lock-statement
using-statement
yield-statement
The embedded-statement nonterminal is used for statements that appear within other statements. The use of an embedded-statement rather than a statement excludes the use of declaration statements and labeled statements in these contexts. The example
void F(bool b) {
if (b)
int i = 44;
}
results in a compile-time error because an if
statement requires an embedded-statement rather than a statement for its if
branch. If this code were permitted, then the variable i
would be declared, but could never be used. By placing i
’s declaration in a block, the example becomes valid.
Every statement has an end point. In intuitive terms, the end point of a statement is the location that immediately follows the statement. The execution rules for composite statements (statements that contain embedded statements) specify the action that is taken when control reaches the end point of an embedded statement. For example, when control reaches the end point of a statement in a block, control is transferred to the next statement in the block.
If a statement can possibly be reached by execution, the statement is said to be reachable. Conversely, if there is no possibility that a statement will be executed, the statement is said to be unreachable.
In the example
void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
}
the second invocation of Console.WriteLine
is unreachable because there is no possibility that the statement will be executed.
A warning is reported if the compiler determines that a statement is unreachable. It is specifically not an error for a statement to be unreachable.
To determine whether a particular statement or end point is reachable, the compiler performs flow analysis according to the reachability rules defined for each statement. The flow analysis takes into account the values of constant expressions (§7.18) that control the behavior of statements, but the possible values of nonconstant expressions are not considered. In other words, for purposes of control flow analysis, a nonconstant expression of a given type is considered to have any possible value of that type.
In the example
void F() {
const int i = 1;
if (i == 2) Console.WriteLine("unreachable");
}
the boolean expression of the if
statement is a constant expression because both operands of the ==
operator are constants. As the constant expression is evaluated at compile time, producing the value false
, the Console.WriteLine
invocation is considered unreachable. However, if i
is changed to be a local variable
void F() {
int i = 1;
if (i == 2) Console.WriteLine("reachable");
}
the Console.WriteLine
invocation is considered reachable, even though, in reality, it will never be executed.
The block of a function member is always considered reachable. By successively evaluating the reachability rules of each statement in a block, the reachability of any given statement can be determined.
In the example
void F(int x) {
Console.WriteLine("start");
if (x < 0) Console.WriteLine("negative");
}
the reachability of the second Console.WriteLine
is determined as follows:
• The first Console.WriteLine
expression statement is reachable because the block of the F
method is reachable.
• The end point of the first Console.WriteLine
expression statement is reachable because that statement is reachable.
• The if
statement is reachable because the end point of the first Console.WriteLine
expression statement is reachable.
• The second Console.WriteLine
expression statement is reachable because the boolean expression of the if
statement does not have the constant value false
.
There are two situations in which it is a compile-time error for the end point of a statement to be reachable:
• Because the switch
statement does not permit a switch section to “fall through” to the next switch section, it is a compile-time error for the end point of the statement list of a switch section to be reachable. If this error occurs, it is typically an indication that a break
statement is missing.
• It is a compile-time error for the end point of the block of a function member that computes a value to be reachable. If this error occurs, it is typically an indication that a return
statement is missing.
A block permits multiple statements to be written in contexts where a single statement is allowed.
block:
{ statement-listopt }
A block consists of an optional statement-list (§8.2.1), enclosed in braces. If the statement list is omitted, the block is said to be empty.
A block may contain declaration statements (§8.5). The scope of a local variable or constant declared in a block is the block.
Within a block, the meaning of a name used in an expression context must always be the same (§7.5.2.1).
A block is executed as follows:
• If the block is empty, control is transferred to the end point of the block.
• If the block is not empty, control is transferred to the statement list. When and if control reaches the end point of the statement list, control is transferred to the end point of the block.
The statement list of a block is reachable if the block itself is reachable.
The end point of a block is reachable if the block is empty or if the end point of the statement list is reachable.
A block that contains one or more yield
statements (§8.14) is called an iterator block. Iterator blocks are used to implement function members as iterators (§10.14). Some additional restrictions apply to iterator blocks:
• It is a compile-time error for a return
statement to appear in an iterator block (but yield return
statements are permitted).
• It is a compile-time error for an iterator block to contain an unsafe context (§18.1). An iterator block always defines a safe context, even when its declaration is nested in an unsafe context.
A statement list consists of one or more statements written in sequence. Statement lists occur in blocks (§8.2) and in switch-blocks (§8.7.2).
statement-list:
statement statement-list
statement
A statement list is executed by transferring control to the first statement. When and if control reaches the end point of a statement, control is transferred to the next statement. When and if control reaches the end point of the last statement, control is transferred to the end point of the statement list.
A statement in a statement list is reachable if at least one of the following is true:
• The statement is the first statement and the statement list itself is reachable.
• The end point of the preceding statement is reachable.
• The statement is a labeled statement and the label is referenced by a reachable goto
statement.
The end point of a statement list is reachable if the end point of the last statement in the list is reachable.
An empty-statement does nothing.
empty-statement:
;
An empty statement is used when there are no operations to perform in a context where a statement is required.
Execution of an empty statement simply transfers control to the end point of the statement. Thus the end point of an empty statement is reachable if the empty statement is reachable.
An empty statement can be used when writing a while
statement with a null body:
bool ProcessMessage() {…}
void ProcessMessages() {
while (ProcessMessage())
;
}
In addition, an empty statement can be used to declare a label just before the closing “}
” of a block:
void F() {
…
if (done) goto exit;
…
exit: ;
}
A labeled-statement permits a statement to be prefixed by a label. Labeled statements are permitted in blocks, but are not permitted as embedded statements.
labeled-statement:
identifier : statement
A labeled statement declares a label with the name given by the identifier. The scope of a label is the whole block in which the label is declared, including any nested blocks. It is a compile-time error for two labels with the same name to have overlapping scopes.
A label can be referenced from goto
statements (§8.9.3) within the scope of the label. As a consequence, goto
statements can transfer control within blocks and out of blocks, but never into blocks.
Labels have their own declaration space and do not interfere with other identifiers. The example
int F(int x) {
if (x >= 0) goto x;
x = -x;
x: return x;
}
is valid and uses the name x
as both a parameter and a label.
Execution of a labeled statement corresponds exactly to execution of the statement following the label.
In addition to the reachability provided by the normal flow of control, a labeled statement is reachable if the label is referenced by a reachable goto
statement. (Exception: If a goto
statement is inside a try
that includes a finally
block, and the labeled statement is outside the try
, and the end point of the finally
block is unreachable, then the labeled statement is not reachable from that goto
statement.)
A declaration-statement declares a local variable or constant. Declaration statements are permitted in blocks, but are not permitted as embedded statements.
declaration-statement:
local-variable-declaration ;
local-constant-declaration
A local-variable-declaration declares one or more local variables.
local-variable-declaration:
local-variable-type local-variable-declarators
local-variable-type:
type
var
local-variable-declarators:
local-variable-declarator
local-variable-declarators , local-variable-declarator
local-variable-declarator:
identifier
identifier = local-variable-initializer
local-variable-initializer:
expression
array-initializer
The local-variable-type of a local-variable-declaration either directly specifies the type of the variables introduced by the declaration or indicates with the keyword var
that the type should be inferred based on an initializer. The type is followed by a list of local-variable-declarators, each of which introduces a new variable. A local-variable-declarator consists of an identifier that names the variable, optionally followed by an “=
” token and a local-variable-initializer that gives the initial value of the variable.
When the local-variable-type is specified as var
and no type named var
is in scope, the declaration is an implicitly typed local variable declaration, whose type is inferred from the type of the associated initializer expression. Implicitly typed local variable declarations are subject to the following restrictions:
• The local-variable-declaration cannot include multiple local-variable-declarators.
• The local-variable-declarator must include a local-variable-initializer.
• The local-variable-initializer must be an expression.
• The initializer expression must have a compile-time type.
The initializer expression cannot refer to the declared variable itself
The following are examples of incorrect implicitly typed local variable declarations:
var x; // Error, no initializer to infer type from
var y = {1, 2, 3}; // Error, array initializer not permitted
var z = null; // Error, null does not have a type
var u = x => x + 1; // Error, anonymous functions do not have a type
var v = v++; // Error, initializer cannot refer to variable itself
The value of a local variable is obtained in an expression using a simple-name (§7.5.2), and the value of a local variable is modified using an assignment (§7.16). A local variable must be definitely assigned (§5.3) at each location where its value is obtained.
The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs. It is an error to refer to a local variable in a textual position that precedes the local-variable-declarator of the local variable. Within the scope of a local variable, it is a compile-time error to declare another local variable or constant with the same name.
A local variable declaration that declares multiple variables is equivalent to multiple declarations of single variables with the same type. Furthermore, a variable initializer in a local variable declaration corresponds exactly to an assignment statement that is inserted immediately after the declaration.
The example
void F() {
int x = 1, y, z = x * 2;
}
corresponds exactly to
void F() {
int x; x = 1;
int y;
int z; z = x * 2;
}
In an implicitly typed local variable declaration, the type of the local variable being declared is taken to be the same as the type of the expression used to initialize the variable. For example,
var i = 5;
var s = "Hello";
var d = 1.0; var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
The implicitly typed local variable declarations in this example are precisely equivalent to the following explicitly typed declarations:
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
A local-constant-declaration declares one or more local constants.
local-constant-declaration:
const
type constant-declarators
constant-declarators:
constant-declarator
constant-declarators ,
constant-declarator
constant-declarator:
identifier = constant-expression
The type of a local-constant-declaration specifies the type of the constants introduced by the declaration. The type is followed by a list of constant-declarators, each of which introduces a new constant. A constant-declarator consists of an identifier that names the constant, followed by an “=
” token, followed by a constant-expression (§7.18) that gives the value of the constant.
The type and constant-expression of a local constant declaration must follow the same rules as those of a constant member declaration (§10.4).
The value of a local constant is obtained in an expression using a simple-name (§7.5.2).
The scope of a local constant is the block in which the declaration occurs. It is an error to refer to a local constant in a textual position that precedes its constant-declarator. Within the scope of a local constant, it is a compile-time error to declare another local variable or constant with the same name.
A local constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same type.
An expression-statement evaluates a given expression. The value computed by the expression, if any, is discarded.
expression-statement:
statement-expression
statement-expression:
invocation-expression
object-creation-expression
assignment
post-increment-expression
post-decrement-expression
pre-increment-expression
pre-decrement-expression
Not all expressions are permitted as statements. In particular, expressions such as x + y
and x == 1
that merely compute a value (which will subsequently be discarded) are not permitted as statements.
Execution of an expression-statement evaluates the contained expression and then transfers control to the end point of the expression-statement. The end point of an expression-statement is reachable if that expression-statement is reachable.
Selection statements select one of a number of possible statements for execution based on the value of some expression.
selection-statement:
if-statement
switch-statement
The if
statement selects a statement for execution based on the value of a boolean expression.
if-statement:
if (
boolean-expression )
embedded-statement
if (
boolean-expression )
embedded-statement else
embedded-statement
An else
part is associated with the lexically nearest preceding if
that is allowed by the syntax. Thus, an if
statement of the form
if (x) if (y) F(); else G();
is equivalent to
if (x) {
if (y) {
F();
}
else {
G();
}
}
An if
statement is executed as follows:
• The boolean-expression (§7.19) is evaluated.
• If the boolean expression yields true
, control is transferred to the first embedded statement. When and if control reaches the end point of that statement, control is transferred to the end point of the if
statement.
• If the boolean expression yields false
and if an else
part is present, control is transferred to the second embedded statement. When and if control reaches the end point of that statement, control is transferred to the end point of the if
statement.
• If the boolean expression yields false
and if an else
part is not present, control is transferred to the end point of the if
statement.
The first embedded statement of an if
statement is reachable if the if
statement is reachable and the boolean expression does not have the constant value false
.
The second embedded statement of an if
statement, if present, is reachable if the if
statement is reachable and the boolean expression does not have the constant value true
.
The end point of an if
statement is reachable if the end point of at least one of its embedded statements is reachable. In addition, the end point of an if
statement with no else
part is reachable if the if
statement is reachable and the boolean expression does not have the constant value true
.
The switch
statement selects for execution a statement list having an associated switch label that corresponds to the value of the switch expression.
switch-statement:
switch
(
expression )
switch-block
switch-block:
{
switch-sectionsopt }
switch-sections:
switch-section
switch-sections switch-section
switch-section:
switch-labels statement-list
switch-labels:
switch-label
switch-labels switch-label
switch-label:
case
constant-expression :
default :
A switch-statement consists of the keyword switch
, followed by a parenthesized expression (called the switch expression), followed by a switch-block. The switch-block consists of zero or more switch-sections, enclosed in braces. Each switch-section consists of one or more switch-labels followed by a statement-list (§8.2.1).
The governing type of a switch
statement is established by the switch expression.
• If the type of the switch expression is sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, bool
, char
, string
, or an enum-type, or if it is the nullable type corresponding to one of these types, then that is the governing type of the switch
statement.
• Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, or string
, or a nullable type corresponding to one of those types.
• Otherwise, if no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs.
The constant expression of each case
label must denote a value of a type that is implicitly convertible (§6.1) to the governing type of the switch
statement. A compile-time error occurs if two or more case
labels in the same switch
statement specify the same constant value.
There can be at most one default
label in a switch statement.
A switch
statement is executed as follows:
• The switch expression is evaluated and converted to the governing type.
• If one of the constants specified in a case
label in the same switch
statement is equal to the value of the switch expression, control is transferred to the statement list following the matched case
label.
• If none of the constants specified in case
labels in the same switch
statement is equal to the value of the switch expression, and if a default
label is present, control is transferred to the statement list following the default
label.
• If none of the constants specified in case
labels in the same switch
statement is equal to the value of the switch expression, and if no default
label is present, control is transferred to the end point of the switch
statement.
If the end point of the statement list of a switch section is reachable, a compile-time error occurs. This is known as the “no fall-through” rule. The example
switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
default:
CaseOthers();
break;
}
is valid because no switch section has a reachable end point. Unlike in C and C++, execution of a switch section is not permitted to “fall through” to the next switch section, and the example
switch (i) {
case 0:
CaseZero();
case 1:
CaseZeroOrOne();
default:
CaseAny();
}
results in a compile-time error. When execution of a switch section is to be followed by execution of another switch section, an explicit goto case
or goto default
statement must be used:
switch (i) {
case 0:
CaseZero();
goto case 1;
case 1:
CaseZeroOrOne();
goto default;
default:
CaseAny();
break;
}
Multiple labels are permitted in a switch-section. The example
switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
case 2:
default:
CaseTwo();
break;
}
is valid. The example does not violate the “no fall-through” rule because the labels case 2:
and default:
are part of the same switch-section.
The “no fall-through” rule prevents a common class of bugs that occur in C and C++ when break
statements are accidentally omitted. In addition, because of this rule, the switch sections of a switch
statement can be arbitrarily rearranged without affecting the behavior of the statement. For example, the sections of the switch
statement above can be reversed without affecting the behavior of the statement:
switch (i) {
default:
CaseAny();
break;
case 1:
CaseZeroOrOne();
goto default;
case 0:
CaseZero();
goto case 1;
}
The statement list of a switch section typically ends in a break
, goto case
, or goto default
statement, but any construct that renders the end point of the statement list unreachable is permitted. For example, a while
statement controlled by the boolean expression true
is known to never reach its end point. Likewise, a throw
or return
statement always transfers control elsewhere and never reaches its end point. Thus the following example is valid:
switch (i) {
case 0:
while (true) F();
case 1:
throw new ArgumentException();
case 2:
return;
}
The governing type of a switch
statement may be the type string
. For example,
void DoCommand(string command) {
switch (command.ToLower()) {
case "run":
DoRun();
break;
case "save":
DoSave();
break;
case "quit":
DoQuit();
break;
default:
InvalidCommand(command);
break;
}
}
Like the string equality operators (§7.9.7), the switch
statement is case sensitive and will execute a given switch section only if the switch expression string exactly matches a case
label constant.
When the governing type of a switch
statement is string
, the value null
is permitted as a case
label constant.
The statement-lists of a switch-block may contain declaration statements (§8.5). The scope of a local variable or constant declared in a switch block is the switch block.
Within a switch block, the meaning of a name used in an expression context must always be the same (§7.5.2.1).
The statement list of a given switch section is reachable if the switch
statement is reachable and at least one of the following is true:
• The switch expression is a nonconstant value.
• The switch expression is a constant value that matches a case
label in the switch section.
• The switch expression is a constant value that doesn’t match any case
label, and the switch section contains the default
label.
• A switch label of the switch section is referenced by a reachable goto case
or goto default
statement.
The end point of a switch
statement is reachable if at least one of the following is true:
• The switch
statement contains a reachable break
statement that exits the switch
statement.
• The switch
statement is reachable, the switch expression is a nonconstant value, and no default
label is present.
• The switch
statement is reachable, the switch expression is a constant value that doesn’t match any case
label, and no default
label is present.
Iteration statements repeatedly execute an embedded statement.
iteration-statement:
while-statement
do-statement
for-statement
foreach-statement
The while
statement conditionally executes an embedded statement zero or more times.
while-statement:
while (
boolean-expression )
embedded-statement
A while
statement is executed as follows:
• The boolean-expression (§7.19) is evaluated.
• If the boolean expression yields true
, control is transferred to the embedded statement. When and if control reaches the end point of the embedded statement (possibly from execution of a continue
statement), control is transferred to the beginning of the while
statement.
• If the boolean expression yields false
, control is transferred to the end point of the while
statement.
Within the embedded statement of a while
statement, a break
statement (§8.9.1) may be used to transfer control to the end point of the while
statement (thus ending iteration of the embedded statement), and a continue
statement (§8.9.2) may be used to transfer control to the end point of the embedded statement (thus performing another iteration of the while
statement).
The embedded statement of a while
statement is reachable if the while
statement is reachable and the boolean expression does not have the constant value false
.
The end point of a while
statement is reachable if at least one of the following is true:
• The while
statement contains a reachable break
statement that exits the while
statement.
• The while
statement is reachable and the boolean expression does not have the constant value true
.
The do
statement conditionally executes an embedded statement one or more times.
do-statement:
do
embedded-statement while (
boolean-expression ) ;
A do
statement is executed as follows:
• Control is transferred to the embedded statement.
• When and if control reaches the end point of the embedded statement (possibly from execution of a continue
statement), the boolean-expression (§7.19) is evaluated. If the boolean expression yields true
, control is transferred to the beginning of the do
statement. Otherwise, control is transferred to the end point of the do
statement.
Within the embedded statement of a do
statement, a break
statement (§8.9.1) may be used to transfer control to the end point of the do
statement (thus ending iteration of the embedded statement), and a continue
statement (§8.9.2) may be used to transfer control to the end point of the embedded statement.
The embedded statement of a do
statement is reachable if the do
statement is reachable.
The end point of a do
statement is reachable if at least one of the following is true:
• The do
statement contains a reachable break
statement that exits the do
statement.
• The end point of the embedded statement is reachable and the boolean expression does not have the constant value true
.
The for
statement evaluates a sequence of initialization expressions and then, while a condition is true, repeatedly executes an embedded statement and evaluates a sequence of iteration expressions.
for-statement:
for (
for-initializeropt ;
for-conditionopt ;
for-iteratoropt )
embedded-statement
for-initializer:
local-variable-declaration
statement-expression-list
for-condition:
boolean-expression
for-iterator:
statement-expression-list
statement-expression-list:
statement-expression
statement-expression-list ,
statement-expression
The for-initializer, if present, consists of either a local-variable-declaration (§8.5.1) or a list of statement-expressions (§8.6) separated by commas. The scope of a local variable declared by a for-initializer starts at the local-variable-declarator for the variable and extends to the end of the embedded statement. The scope includes the for-condition and the for-iterator.
The for-condition, if present, must be a boolean-expression (§7.19).
The for-iterator, if present, consists of a list of statement-expressions (§8.6) separated by commas.
A for statement is executed as follows:
• If a for-initializer is present, the variable initializers or statement expressions are executed in the order they are written. This step is performed only once.
• If a for-condition is present, it is evaluated.
• If the for-condition is not present or if the evaluation yields true
, control is transferred to the embedded statement. When and if control reaches the end point of the embedded statement (possibly from execution of a continue
statement), the expressions of the for-iterator, if any, are evaluated in sequence, and then another iteration is performed, starting with evaluation of the for-condition in the step above.
• If the for-condition is present and the evaluation yields false
, control is transferred to the end point of the for
statement.
Within the embedded statement of a for
statement, a break
statement (§8.9.1) may be used to transfer control to the end point of the for
statement (thus ending iteration of the embedded statement), and a continue
statement (§8.9.2) may be used to transfer control to the end point of the embedded statement (thus executing the for-iterator and performing another iteration of the for
statement, starting with the for-condition).
The embedded statement of a for
statement is reachable if one of the following is true:
• The for
statement is reachable and no for-condition is present.
• The for
statement is reachable and a for-condition is present and does not have the constant value false
.
The end point of a for
statement is reachable if at least one of the following is true:
• The for
statement contains a reachable break
statement that exits the for
statement.
• The for
statement is reachable and a for-condition is present and does not have the constant value true
.
The foreach
statement enumerates the elements of a collection, executing an embedded statement for each element of the collection.
foreach-statement:
foreach (
local-variable-type identifier in
expression )
embedded-statement
The type and identifier of a foreach
statement declare the iteration variable of the statement. If the var
keyword is given as the local-variable-type, the iteration variable is said to be an implicitly typed iteration variable, and its type is taken to be the element type of the foreach
statement, as specified below. The iteration variable corresponds to a read-only local variable with a scope that extends over the embedded statement. During execution of a foreach
statement, the iteration variable represents the collection element for which an iteration is currently being performed. A compile-time error occurs if the embedded statement attempts to modify the iteration variable (via assignment or the ++
and -
operators) or pass the iteration variable as a ref
or out
parameter.
The compile-time processing of a foreach
statement first determines the collection type, enumerator type, and element type of the expression. This determination proceeds as follows:
• If the type X
of expression is an array type, then there is an implicit reference conversion from X
to the System.Collections.IEnumerable
interface (because System.Array
implements this interface). The collection type is the System.Collections.IEnumerable
interface, the enumerator type is the System.Collections.IEnumerator
interface, and the element type is the element type of the array type X
.
• Otherwise, determine whether the type X
has an appropriate GetEnumerator
method:
- Perform member lookup on the type X
with identifier GetEnumerator
and no type arguments. If the member lookup does not produce a match, produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.
- Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described following. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods.
- If the return type E
of the GetEnumerator
method is not a class, struct, or interface type, an error is produced and no further steps are taken.
- Member lookup is performed on E
with the identifier Current
and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.
- Member lookup is performed on E
with the identifier MoveNext
and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken.
- Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not bool
, an error is produced and no further steps are taken.
- The collection type is X
, the enumerator type is E
, and the element type is the type of the Current
property.
• Otherwise, check for an enumerable interface:
- If there is exactly one type T
such that there is an implicit conversion from X
to the interface System.Collections.Generic.IEnumerable<T>
, then the collection type is this interface, the enumerator type is the interface System.Collections.Generic.IEnumerator<T>
, and the element type is T
.
- Otherwise, if there is more than one such type T
, then an error is produced and no further steps are taken.
- Otherwise, if there is an implicit conversion from X
to the System.Collections.IEnumerable
interface, then the collection type is this interface, the enumerator type is the interface System.Collections.IEnumerator
, and the element type is object
.
- Otherwise, an error is produced and no further steps are taken.
These steps, if successful, unambiguously produce a collection type C
, enumerator type E
, and element type T
. A foreach
statement of the form
foreach (V v in x)
embedded-statement
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose of e
}
}
The variable e
is not visible to or accessible to the expression x
, the embedded statement, or any other source code of the program. The variable v
is read-only in the embedded statement. If there is not an explicit conversion (§6.2) from T
(the element type) to V
(the local-variable-type in the foreach
statement), an error is produced and no further steps are taken. If x
has the value null
, a System.NullReferenceException
is thrown at runtime.
An implementation is permitted to implement a given foreach-statement differently—for example, for performance reasons—as long as the behavior is consistent with the above expansion.
The body of the finally
block is constructed according to the following steps:
• If there is an implicit conversion from E
to the System.IDisposable
interface, then
- If E
is a non-nullable value type, then the finally
clause is expanded to the semantic equivalent of
finally {
((System.IDisposable)e).Dispose();
}
- Otherwise, the finally
clause is expanded to the semantic equivalent of
finally {
if (e != null) ((System.IDisposable)e).Dispose();
}
except that if E
is a value type, or a type parameter instantiated to a value type, then the cast of e
to System.IDisposable
will not cause boxing to occur.
• Otherwise, if E
is a sealed type, the finally
clause is expanded to an empty block:
finally {
}
• Otherwise, the finally
clause is expanded to
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
The local variable d
is not visible to or accessible to any user code. In particular, it does not conflict with any other variable whose scope includes the finally
block.
The order in which foreach
traverses the elements of an array is as follows: For single-dimensional arrays, elements are traversed in increasing index order, starting with index 0
and ending with index Length – 1
. For multi-dimensional arrays, elements are traversed such that the indices of the rightmost dimension are increased first, then the next left dimension, and so on to the left.
The following example prints out each value in a two-dimensional array, in element order:
using System;
class Test
{
static void Main() {
double[,] values = {
{1.2, 2.3, 3.4, 4.5},
{5.6, 6.7, 7.8, 8.9}
};
foreach (double elementValue in values)
Console.Write("{0} ", elementValue);
Console.WriteLine();
}
}
The output produced is as follows:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9
In the example
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
the type of n
is inferred to be int
, the element type of numbers
.
Jump statements unconditionally transfer control.
jump-statement:
break-statement
continue-statement
goto-statement
return-statement
throw-statement
The location to which a jump statement transfers control is called the target of the jump statement.
When a jump statement occurs within a block, and the target of that jump statement is outside that block, the jump statement is said to exit the block. Although a jump statement may transfer control out of a block, it can never transfer control into a block.
Execution of jump statements is complicated by the presence of intervening try
statements. In the absence of such try
statements, a jump statement unconditionally transfers control from the jump statement to its target. In the presence of intervening try
statements, execution is more complex. If the jump statement exits one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all intervening try
statements have been executed.
In the example
using System;
class Test
{
static void Main() {
while (true) {
try {
try {
Console.WriteLine("Before break");
break;
}
finally {
Console.WriteLine("Innermost finally block");
}
}
finally {
Console.WriteLine("Outermost finally block");
}
}
Console.WriteLine("After break");
}
}
the finally
blocks associated with two try
statements are executed before control is transferred to the target of the jump statement.
The output produced is as follows:
Before break
Innermost finally block
Outermost finally block
After break
The break
statement exits the nearest enclosing switch
, while
, do
, for
, or foreach
statement.
break-statement:
break
The target of a break
statement is the end point of the nearest enclosing switch
, while
, do
, for
, or foreach
statement. If a break
statement is not enclosed by a switch
, while
, do
, for
, or foreach
statement, a compile-time error occurs.
When multiple switch
, while
, do
, for
, or foreach
statements are nested within one another, a break
statement applies only to the innermost statement. To transfer control across multiple nesting levels, a goto
statement (§8.9.3) must be used.
A break
statement cannot exit a finally
block (§8.10). When a break
statement occurs within a finally
block, the target of the break
statement must be within the same finally
block; otherwise, a compile-time error occurs.
A break
statement is executed as follows:
• If the break
statement exits one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all intervening try
statements have been executed.
• Control is transferred to the target of the break
statement.
Because a break
statement unconditionally transfers control elsewhere, the end point of a break
statement is never reachable.
The continue
statement starts a new iteration of the nearest enclosing while
, do
, for
, or foreach
statement.
continue-statement:
continue ;
The target of a continue
statement is the end point of the embedded statement of the nearest enclosing while
, do
, for
, or foreach
statement. If a continue
statement is not enclosed by a while
, do
, for
, or foreach
statement, a compile-time error occurs.
When multiple while
, do
, for
, or foreach
statements are nested within one another, a continue
statement applies only to the innermost statement. To transfer control across multiple nesting levels, a goto
statement (§8.9.3) must be used.
A continue
statement cannot exit a finally
block (§8.10). When a continue
statement occurs within a finally
block, the target of the continue
statement must be within the same finally
block; otherwise a compile-time error occurs.
A continue
statement is executed as follows:
• If the continue
statement exits one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all intervening try
statements have been executed.
• Control is transferred to the target of the continue
statement.
Because a continue
statement unconditionally transfers control elsewhere, the end point of a continue
statement is never reachable.
The goto
statement transfers control to a statement that is marked by a label.
goto-statement:
goto
identifier
goto case
constant-expression ;
goto default;
The target of a goto
identifier statement is the labeled statement with the given label. If a label with the given name does not exist in the current function member, or if the goto
statement is not within the scope of the label, a compile-time error occurs. This rule permits the use of a goto
statement to transfer control out of a nested scope, but not into a nested scope. In the example
using System;
class Test {
static void Main(string[] args) {
string[,] table = {
{"Red", "Blue", "Green"},
{"Monday", "Wednesday", "Friday"}
};
foreach (string str in args) {
int row, colm;
for (row = 0; row <= 1; ++row)
for (colm = 0; colm <= 2; ++colm)
if (str == table[row,colm])
goto done;
Console.WriteLine("{0} not found", str);
continue;
done:
Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm);
}
}
}
a goto
statement is used to transfer control out of a nested scope.
The target of a goto case
statement is the statement list in the immediately enclosing switch
statement (§8.7.2), which contains a case
label with the given constant value. If the goto case
statement is not enclosed by a switch
statement, if the constant-expression is not implicitly convertible (§6.1) to the governing type of the nearest enclosing switch
statement, or if the nearest enclosing switch
statement does not contain a case
label with the given constant value, a compile-time error occurs.
The target of a goto default
statement is the statement list in the immediately enclosing switch
statement (§8.7.2), which contains a default
label. If the goto default
statement is not enclosed by a switch
statement, or if the nearest enclosing switch
statement does not contain a default
label, a compile-time error occurs.
A goto
statement cannot exit a finally
block (§8.10). When a goto
statement occurs within a finally
block, the target of the goto
statement must be within the same finally
block, or otherwise a compile-time error occurs.
A goto
statement is executed as follows:
• If the goto
statement exits one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all intervening try
statements have been executed.
• Control is transferred to the target of the goto
statement.
Because a goto
statement unconditionally transfers control elsewhere, the end point of a goto
statement is never reachable.
The return
statement returns control to the caller of the function member in which the return
statement appears.
return-statement:
return
expressionopt ;
A return
statement with no expression can be used only in a function member that does not compute a value—that is, a method with the return type void
, the set
accessor of a property or indexer, the add
and remove
accessors of an event, an instance constructor, a static constructor, or a destructor.
A return
statement with an expression can be used only in a function member that computes a value—that is, a method with a non-void return type, the get
accessor of a property or indexer, or a user-defined operator. An implicit conversion (§6.1) must exist from the type of the expression to the return type of the containing function member.
It is a compile-time error for a return
statement to appear in a finally
block (§8.10).
A return
statement is executed as follows:
• If the return
statement specifies an expression, the expression is evaluated and the resulting value is converted to the return type of the containing function member by an implicit conversion. The result of the conversion becomes the value returned to the caller.
• If the return
statement is enclosed by one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all enclosing try
statements have been executed.
• Control is returned to the caller of the containing function member.
Because a return
statement unconditionally transfers control elsewhere, the end point of a return
statement is never reachable.
The throw
statement throws an exception.
throw-statement:
throw
expressionopt ;
A throw
statement with an expression throws the value produced by evaluating the expression. The expression must denote a value of the class type System.Exception
, of a class type that derives from System.Exception
or of a type parameter type that has System.Exception
(or a subclass thereof) as its effective base class. If evaluation of the expression produces null
, a System.NullReferenceException
is thrown instead.
A throw
statement with no expression can be used only in a catch
block, in which case the throw
statement rethrows the exception that is currently being handled by that catch
block.
Because a throw
statement unconditionally transfers control elsewhere, the end point of a throw
statement is never reachable.
When an exception is thrown, control is transferred to the first catch
clause in an enclosing try
statement that can handle the exception. The process that takes place from the point of the exception being thrown to the point of transferring control to a suitable exception handler is known as exception propagation. Propagation of an exception consists of repeatedly evaluating the following steps until a catch
clause that matches the exception is found. In this description, the throw point is initially the location at which the exception is thrown.
• In the current function member, each try
statement that encloses the throw point is examined. For each statement S
, starting with the innermost try
statement and ending with the outermost try
statement, the following steps are evaluated:
- If the try
block of S
encloses the throw point and if S has one or more catch
clauses, the catch
clauses are examined in order of appearance to locate a suitable handler for the exception. The first catch
clause that specifies the exception type or a base type of the exception type is considered a match. A general catch
clause (§8.10) is considered a match for any exception type. If a matching catch
clause is located, the exception propagation is completed by transferring control to the block of that catch
clause.
- Otherwise, if the try
block or a catch
block of S
encloses the throw point and if S
has a finally
block, control is transferred to the finally
block. If the finally
block throws another exception, processing of the current exception is terminated. Otherwise, when control reaches the end point of the finally
block, processing of the current exception is continued.
• If an exception handler was not located in the current function member invocation, the function member invocation is terminated. The steps above are then repeated for the caller of the function member with a throw point corresponding to the statement from which the function member was invoked.
• If the exception processing terminates all function member invocations in the current thread, indicating that the thread has no handler for the exception, then the thread is itself terminated. The impact of such termination is implementation-defined.
The try
statement provides a mechanism for catching exceptions that occur during execution of a block. Furthermore, the try
statement provides the ability to specify a block of code that is always executed when control leaves the try
statement.
try-statement:
try
block catch-clauses
try
block finally-clause
try
block catch-clauses finally-clause
catch-clauses:
specific-catch-clauses general-catch-clauseopt
specific-catch-clausesopt general-catch-clause
specific-catch-clauses:
specific-catch-clause
specific-catch-clauses specific-catch-clause
specific-catch-clause:
catch (
class-type identifieropt )
block
general-catch-clause:
catch
block
finally-clause:
finally
block
There are three possible forms of try
statements:
• A try
block followed by one or more catch
blocks
• A try
block followed by a finally
block
• A try
block followed by one or more catch
blocks followed by a finally
block
When a catch
clause specifies a class-type, the type must be System.Exception
, a type that derives from System.Exception
, or a type parameter type that has System.Exception
(or a subclass thereof) as its effective base class.
When a catch
clause specifies both a class-type and an identifier, an exception variable of the given name and type is declared. The exception variable corresponds to a local variable with a scope that extends over the catch
block. During execution of the catch
block, the exception variable represents the exception currently being handled. For purposes of definite assignment checking, the exception variable is considered definitely assigned in its entire scope.
Unless a catch
clause includes an exception variable name, it is impossible to access the exception object in the catch
block.
A catch
clause that specifies neither an exception type nor an exception variable name is called a general catch
clause. A try
statement can have only one general catch
clause; if one is present, it must be the last catch
clause.
Some programming languages may support exceptions that are not representable as objects derived from System.Exception
, although such exceptions could never be generated by C# code. A general catch
clause may be used to catch such exceptions. Thus a general catch
clause is semantically different from one that specifies the type System.Exception
, in that the former may also catch exceptions from other languages.
In an effort to locate a handler for an exception, catch
clauses are examined in lexical order. A compile-time error occurs if a catch
clause specifies a type that is the same as, or is derived from, a type that was specified in an earlier catch
clause for the same try
. Without this restriction, it would be possible to write unreachable catch
clauses.
Within a catch
block, a throw
statement (§8.9.5) with no expression can be used to rethrow the exception that was caught by the catch
block. Assignments to an exception variable do not alter the exception that is rethrown.
In the example
using System;
class Test
{
static void F() {
try {
G();
}
catch (Exception e) {
Console.WriteLine("Exception in F: " + e.Message);
e = new Exception("F");
throw; // Rethrow
}
}
static void G() {
throw new Exception("G");
}
static void Main() {
try {
F();
}
catch (Exception e) {
Console.WriteLine("Exception in Main: " + e.Message);
}
}
}
the method F
catches an exception, writes some diagnostic information to the console, alters the exception variable, and rethrows the exception. The exception that is rethrown is the original exception, so the following output is produced:
Exception in F: G
Exception in Main: G
If the first catch
block had thrown e
instead of rethrowing the current exception, the following output would be produced:
Exception in F: G
Exception in Main: F
It is a compile-time error for a break
, continue
, or goto
statement to transfer control out of a finally
block. When a break
, continue
, or goto
statement occurs in a finally
block, the target of the statement must be within the same finally
block; otherwise, a compile-time error occurs.
It is a compile-time error for a return
statement to occur in a finally
block.
A try
statement is executed as follows:
• Control is transferred to the try
block.
• When and if control reaches the end point of the try
block:
- If the try
statement has a finally
block, the finally
block is executed.
- Control is transferred to the end point of the try
statement.
• If an exception is propagated to the try
statement during execution of the try
block:
- The catch
clauses, if any, are examined in order of appearance to locate a suitable handler for the exception. The first catch
clause that specifies the exception type or a base type of the exception type is considered a match. A general catch
clause is considered a match for any exception type. If a matching catch
clause is located:
• If the matching catch
clause declares an exception variable, the exception object is assigned to the exception variable.
• Control is transferred to the matching catch
block.
• When and if control reaches the end point of the catch
block:
- If the try
statement has a finally
block, the finally
block is executed.
- Control is transferred to the end point of the try
statement.
• If an exception is propagated to the try
statement during execution of the catch
block:
- If the try
statement has a finally
block, the finally
block is executed.
- The exception is propagated to the next enclosing try
statement.
- If the try
statement has no catch
clauses or if no catch
clause matches the exception:
• If the try
statement has a finally
block, the finally
block is executed.
• The exception is propagated to the next enclosing try
statement.
The statements of a finally
block are always executed when control leaves a try
statement. This is true whether the control transfer occurs as a result of normal execution; as a result of executing a break
, continue
, goto
, or return
statement; or as a result of propagating an exception out of the try
statement.
If an exception is thrown during execution of a finally
block and is not caught within the same finally
block, the exception is propagated to the next enclosing try
statement. If another exception was in the process of being propagated, that exception is lost. The process of propagating an exception is discussed further in the description of the throw
statement (§8.9.5).
The try
block of a try
statement is reachable if the try
statement is reachable.
A catch
block of a try
statement is reachable if the try
statement is reachable.
The finally
block of a try
statement is reachable if the try
statement is reachable.
The end point of a try
statement is reachable if both of the following are true:
• The end point of the try
block is reachable or the end point of at least one catch
block is reachable.
• If a finally
block is present, the end point of the finally
block is reachable.
The checked
and unchecked
statements are used to control the overflow checking context for integral-type arithmetic operations and conversions.
checked-statement:
checked
block
unchecked-statement:
unchecked
block
The checked
statement causes all expressions in the block to be evaluated in a checked context, and the unchecked
statement causes all expressions in the block to be evaluated in an unchecked context.
The checked
and unchecked
statements are precisely equivalent to the checked
and unchecked
operators (§7.5.12), except that they operate on blocks instead of expressions.
The lock
statement obtains the mutual-exclusion lock for a given object, executes a statement, and then releases the lock.
lock-statement:
lock (
expression )
embedded-statement
The expression of a lock
statement must denote a value of a type known to be a reference-type. No implicit boxing conversion (§6.1.7) is ever performed for the expression of a lock
statement. For this reason, it is a compile-time error for the expression to denote a value of a value-type.
A lock
statement of the form
lock (x) …
where x
is an expression of a reference-type, is precisely equivalent to
System.Threading.Monitor.Enter(x);
try {
…
}
finally {
System.Threading.Monitor.Exit(x);
}
except that x
is evaluated only once.
While a mutual-exclusion lock is held, code executing in the same execution thread can also obtain and release the lock. In contrast, code executing in other threads is blocked from obtaining the lock until the lock is released.
Locking System.Type
objects so as to synchronize access to static data is not recommended. Other code might lock on the same type, which can result in deadlock. A better approach is to synchronize access to static data by locking a private static object. For example,
class Cache
{
private static object synchronizationObject = new object();
public static void Add(object x) {
lock (Cache.synchronizationObject) {
…
}
}
public static void Remove(object x) {
lock (Cache.synchronizationObject) {
…
}
}
}
The using
statement obtains one or more resources, executes a statement, and then disposes of the resource.
using-statement:
using (
resource-acquisition )
embedded-statement
resource-acquisition:
local-variable-declaration
expression
A resource is a class or struct that implements System.IDisposable
, which includes a single parameterless method named Dispose
. Code that is using a resource can call Dispose
to indicate that the resource is no longer needed. If Dispose
is not called, then automatic disposal eventually occurs as a consequence of garbage collection.
If the form of resource-acquisition is local-variable-declaration, then the type of the local-variable-declaration must be System.IDisposable
or a type that can be implicitly converted to System.IDisposable
. If the form of resource-acquisition is expression, then this expression must be of type System.IDisposable
or a type that can be implicitly converted to System.IDisposable
.
Local variables declared in a resource-acquisition are read-only, and must include an initializer. A compile-time error occurs if the embedded statement attempts to modify these local variables (via assignment or the ++
and operators), take their addresses, or pass them as ref
or out
parameters.
A using
statement is translated into three parts: acquisition, usage, and disposal. Usage of the resource is implicitly enclosed in a try
statement that includes a finally
clause. This finally
clause disposes of the resource. If a null
resource is acquired, then no call to Dispose
is made, and no exception is thrown.
A using
statement of the form
using (ResourceType resource = expression) statement
corresponds to one of two possible expansions. When ResourceType
is a value type, the expansion is
{
ResourceType resource = expression;
try {
statement;
}
finally {
((IDisposable)resource).Dispose();
}
}
Otherwise, when ResourceType
is a reference type, the expansion is
{
ResourceType resource = expression;
try {
statement;
}
finally {
if (resource != null) ((IDisposable)resource).Dispose();
}
}
In either expansion, the resource
variable is read-only in the embedded statement.
An implementation is permitted to implement a given using-statement differently—for example, for performance reasons—as long as the behavior is consistent with the above expansion.
using (expression) statement
has the same two possible expansions, but in this case ResourceType
is implicitly the compile-time type of the expression
, and the resource
variable is inaccessible in, and invisible to, the embedded statement.
When a resource-acquisition takes the form of a local-variable-declaration, it is possible to acquire multiple resources of a given type. A using
statement of the form
using (ResourceType r1 = e1, r2 = e2, …, rN = eN) statement
is precisely equivalent to a sequence of nested using
statements:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
…
using (ResourceType rN = eN)
statement
The following example creates a file named log.txt
and writes two lines of text to the file. The example then opens that same file for reading and copies the contained lines of text to the console.
using System;
using System.IO;
class Test
{
static void Main() {
using (TextWriter w = File.CreateText("log.txt")) {
w.WriteLine("This is line one");
w.WriteLine("This is line two");
}
using (TextReader r = File.OpenText("log.txt")) {
string s;
while ((s = r.ReadLine()) != null) {
Console.WriteLine(s);
}
}
}
}
Because the TextWriter
and TextReader
classes implement the IDisposable
interface, the example can use using
statements to ensure that the underlying file is properly closed following the write or read operations.
The yield
statement is used in an iterator block (§8.2) to yield a value to the enumerator object (§10.14.4) or enumerable object (§10.14.5) of an iterator or to signal the end of the iteration.
yield-statement:
yield return
expression ;
yield break ;
Note that yield
is not a reserved word; it has special meaning only when used immediately before a return
or break
keyword. In other contexts, yield
can be used as an identifier.
Several restrictions dictate where a yield
statement can appear.
• It is a compile-time error for a yield
statement (of either form) to appear outside a method-body, operator-body, or accessor-body.
• It is a compile-time error for a yield
statement (of either form) to appear inside an anonymous function.
• It is a compile-time error for a yield
statement (of either form) to appear in the finally
clause of a try
statement.
• It is a compile-time error for a yield return
statement to appear anywhere in a try
statement that contains any catch
clauses.
The following example shows some valid and invalid uses of yield
statements.
delegate IEnumerable<int> D();
IEnumerator<int> GetEnumerator() {
try {
yield return 1; // Okay
yield break; // Okay
}
finally {
yield return 2; // Error, yield in finally
yield break; // Error, yield in finally
}
try {
yield return 3; // Error, yield return in try…catch
yield break; // Okay
}
catch {
yield return 4; // Error, yield return in try…catch
yield break; // Okay
}
D d = delegate {
yield return 5; // Error, yield in an anonymous function
};
}
int MyMethod() {
yield return 1; // Error, wrong return type for an iterator block
}
An implicit conversion (§6.1) must exist from the type of the expression in the yield return
statement to the yield type (§10.14.3) of the iterator.
A yield return
statement is executed as follows:
• The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the Current
property of the enumerator object.
• Execution of the iterator block is suspended. If the yield return
statement is within one or more try
blocks, the associated finally
blocks are not executed at this time.
• The MoveNext
method of the enumerator object returns true
to its caller, indicating that the enumerator object successfully advanced to the next item.
The next call to the enumerator object’s MoveNext
method resumes execution of the iterator block from where it was last suspended.
A yield break
statement is executed as follows:
• If the yield break
statement is enclosed by one or more try
blocks with associated finally
blocks, control is initially transferred to the finally
block of the innermost try
statement. When and if control reaches the end point of a finally
block, control is transferred to the finally
block of the next enclosing try
statement. This process is repeated until the finally
blocks of all enclosing try
statements have been executed.
• Control is returned to the caller of the iterator block. This is either the MoveNext
method or Dispose
method of the enumerator object.
Because a yield break
statement unconditionally transfers control elsewhere, the end point of a yield break
statement is never reachable.