次の方法で共有


13 ステートメント

13.1 全般

C# には、さまざまなステートメントが用意されています。

: これらのステートメントのほとんどは、C および C++ でプログラミングした開発者にはなじみがあります。 end note

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
    | unsafe_statement   // unsafe code support
    | fixed_statement    // unsafe code support
    ;

unsafe_statement (§23.2) と fixed_statement (§23.7) は、安全でないコード (§23) でのみ使用できます。

embedded_statement非終端は、他のステートメント内に表示されるステートメントに使用されます。 ステートメントではなくembedded_statementを使用するとこれらのコンテキストでの宣言ステートメントとラベル付きステートメントの使用は除外されます。

: コード

void F(bool b)
{
   if (b)
      int i = 44;
}

if ステートメントでは、そのifブランチに対してステートメントではなくembedded_statementが必要であるため、コンパイル時エラーが発生します。 このコードが許可されている場合、変数 i が宣言されますが、使用することはできませんでした。 ただし、 iの宣言をブロックに配置することで、この例は有効であることに注意してください。

end の例

13.2 エンドポイントと到達可能性

すべてのステートメントには、 エンドポイントがあります。 直感的に言えば、ステートメントの終点は、ステートメントの直後の場所です。 複合ステートメント (埋め込みステートメントを含むステートメント) の実行規則では、コントロールが埋め込みステートメントの終点に達したときに実行されるアクションを指定します。

: コントロールがブロック内のステートメントの終点に達すると、コントロールはブロック内の次のステートメントに転送されます。 end の例

実行によってステートメントに到達できる可能性がある場合、ステートメントは 到達可能であると言われます。 逆に、ステートメントが実行される可能性がない場合、ステートメントは 到達不能であると言われます

: 次のコード内

void F()
{
    Console.WriteLine("reachable");
    goto Label;
    Console.WriteLine("unreachable");
  Label:
    Console.WriteLine("reachable");
}

ステートメントが実行される可能性がないため、Console.WriteLine の 2 回目の呼び出しに到達できません。

end の例

throw_statementblock、またはempty_statement以外のステートメントに到達できない場合、警告が報告されます。 具体的には、ステートメントに到達できないエラーではありません。

: 特定のステートメントまたはエンドポイントに到達できるかどうかを判断するために、コンパイラは、各ステートメントに定義されている到達可能性ルールに従ってフロー分析を実行します。 フロー分析では、ステートメントの動作を制御する定数式 (§12.23) の値が考慮されますが、非定数式の使用可能な値は考慮されません。 つまり、制御フロー分析のために、特定の型の非定数式は、その型の任意の値を持つと見なされます。

この例では、

void F()
{
    const int i = 1;
    if (i == 2)
        Console.WriteLine("unreachable");
}

==演算子の両方のオペランドが定数であるため、if ステートメントのブール式は定数式です。 定数式はコンパイル時に評価され、 false値が生成されるため、 Console.WriteLine 呼び出しは到達不能と見なされます。 ただし、 i がローカル変数に変更された場合

void F()
{
    int i = 1;
    if (i == 2)
        Console.WriteLine("reachable");
}

Console.WriteLine呼び出しは到達可能と見なされますが、実際には実行されません。

end note

関数メンバーまたは匿名関数の ブロック は常に到達可能と見なされます。 ブロック内の各ステートメントの到達可能性ルールを連続して評価することで、特定のステートメントの到達可能性を判断できます。

: 次のコード内

void F(int x)
{
    Console.WriteLine("start");
    if (x < 0)
        Console.WriteLine("negative");
}

2 番目の Console.WriteLine の到達可能性は次のように決定されます。

  • F メソッドのブロックに到達可能であるため、最初のConsole.WriteLine式ステートメントに到達できます (§13.3)。
  • 最初の Console.WriteLine 式ステートメントのエンドポイントに到達可能なのは、そのステートメントが到達可能であるためです (§13.7 および §13.3)。
  • 最初のConsole.WriteLine式ステートメントのエンドポイントに到達できるため、if ステートメントに到達できます (§13.7 および §13.3)。
  • if ステートメントのブール式に定数値falseがないため、2 番目のConsole.WriteLine式ステートメントに到達できます。

end の例

ステートメントのエンドポイントに到達できるコンパイル時エラーは、次の 2 つの状況で発生します。

  • switch ステートメントでは、switch セクションが次の switch セクションに "フォールスルー" することは許可されないため、switch セクションのステートメント リストの終了ポイントに到達できるコンパイル時エラーです。 このエラーが発生した場合は、通常、 break ステートメントが見つからないことを示します。

  • これは、到達可能な値を計算する関数メンバーまたは匿名関数のブロックのエンドポイントのコンパイル時エラーです。 このエラーが発生した場合、通常は return ステートメントが見つからないことを示します (§13.10.5)。

13.3 ブロック

13.3.1 全般

"ブロック" を使用すると、1 つのステートメントしか使用できないコンテキストで複数のステートメントを記述できます。

block
    : '{' statement_list? '}'
    ;

block は、オプションのstatement_list (§13.3.2) で構成され、中かっこで囲まれています。 ステートメント リストを省略すると、ブロックは空と見なされます。

ブロックには宣言ステートメント (§13.6) を含められることがあります。 ブロックで宣言されたローカル変数または定数のスコープはブロックです。

ブロックは次のように実行されます。

  • ブロックが空の場合、制御はブロックの終点に転送されます。
  • ブロックが空でない場合は、制御がステートメント・リストに転送されます。 コントロールがステートメント リストの終点に達すると、コントロールはブロックの終点に転送されます。

ブロック自体に到達可能な場合、ブロックのステートメント リストに到達できます。

ブロックが空の場合、またはステートメント リストの終点に到達可能な場合、ブロックの終点に到達できます。

1 つ以上のyield ステートメント (§13.15) を含むブロックは反復子ブロックと呼ばれます。 反復子ブロックは、関数メンバーを反復子として実装するために使用されます (§15.14)。 反復子ブロックには、いくつかの追加の制限が適用されます。

  • これは、 return ステートメントが反復子ブロックに表示されるコンパイル時エラーです (ただし、 yield return ステートメントは許可されています)。
  • 反復子ブロックに安全でないコンテキスト (§23.2) が含まれている場合のコンパイル時エラーです。 反復子ブロックは、安全でないコンテキストで宣言が入れ子になっている場合でも、常に安全なコンテキストを定義します。

13.3.2 ステートメントリスト

ステートメント リストは、順番に記述された 1 つ以上のステートメントで構成されます。 ステートメント リストは、 blocks (§13.3) と switch_block (§13.8.3) で発生します。

statement_list
    : statement+
    ;

ステートメント リストは、コントロールを最初のステートメントに転送することによって実行されます。 コントロールがステートメントの終点に達すると、コントロールは次のステートメントに転送されます。 コントロールが最後のステートメントの終点に達した場合、コントロールはステートメント リストのエンドポイントに転送されます。

次のうち少なくとも 1 つが当てはまる場合、ステートメント リスト内のステートメントに到達できます。

  • ステートメントは最初のステートメントであり、ステートメント・リスト自体に到達可能です。
  • 前のステートメントのエンドポイントに到達可能です。
  • ステートメントはラベル付きステートメントであり、ラベルは到達可能な goto ステートメントによって参照されます。

リスト内の最後のステートメントの終点に到達可能な場合、ステートメント リストの終点に到達できます。

13.4 空のステートメント

empty_statementでは何も行われません。

empty_statement
    : ';'
    ;

空のステートメントは、ステートメントが必要なコンテキストで実行する操作がない場合に使用されます。

空のステートメントを実行すると、単にステートメントのエンドポイントに制御が転送されます。 したがって、空のステートメントに到達可能な場合、空のステートメントのエンドポイントに到達できます。

: null 本文を含む while ステートメントを記述するときに、空のステートメントを使用できます。

bool ProcessMessage() {...}
void ProcessMessages()
{
    while (ProcessMessage())
        ;
}

また、空のステートメントを使用して、ブロックの終了 "}" の直前にラベルを宣言できます。

void F(bool done)
{
    ...
    if (done)
    {
        goto exit;
    }
    ...
  exit:
    ;
}

end の例

13.5 ラベル付きステートメント

labeled_statementでは、ステートメントの先頭にラベルを付けます。 ラベル付きステートメントはブロックで許可されますが、埋め込みステートメントとしては使用できません。

labeled_statement
    : identifier ':' statement
    ;

ラベル付きステートメントは、 identifierによって指定された名前を持つラベルを宣言します。 ラベルのスコープは、入れ子になったブロックを含め、ラベルが宣言されているブロック全体です。 同じ名前の 2 つのラベルに重複するスコープがあると、コンパイル時エラーになります。

ラベルは、 goto ステートメント (§13.10.4) からラベルのスコープ内で参照できます。

: つまり、 goto ステートメントは、ブロック内およびブロック外で制御を転送できますが、ブロックに転送することはできません。 end note

ラベルには独自の宣言領域があり、他の識別子に干渉することはありません。

: 例

int F(int x)
{
    if (x >= 0)
    {
        goto x;
    }
    x = -x;
  x:
    return x;
}

は有効であり、パラメーターとラベルの両方として x という名前を使用します。

end の例

ラベル付きステートメントの実行は、ラベルに続くステートメントの実行に正確に対応します。

通常の制御フローによって提供される到達可能性に加えて、ラベル付きステートメントが到達可能なgoto ステートメントによって参照されている場合、ラベル付きステートメントは到達可能です。ただし、goto ステートメントがtry ブロック内にあるか、エンドポイントに到達できないfinally ブロックを含むtry_statementcatch ブロック内にあり、ラベル付きステートメントがtry_statement外にある場合を除きます。

13.6 宣言ステートメント

13.6.1 全般

declaration_statementは、1 つ以上のローカル変数、1 つ以上のローカル定数、またはローカル関数を宣言します。 宣言ステートメントはブロックおよび switch ブロックで許可されますが、埋め込みステートメントとしては許可されません。

declaration_statement
    : local_variable_declaration ';'
    | local_constant_declaration ';'
    | local_function_declaration
    ;

ローカル変数は、 local_variable_declaration (§13.6.2) を使用して宣言されます。 ローカル定数は、 local_constant_declaration (§13.6.3) を使用して宣言されます。 ローカル関数は、 local_function_declaration (§13.6.4) を使用して宣言されます。

宣言された名前は、最も近い外側の宣言空間 (§7.3) に導入されます。

13.6.2 ローカル変数宣言

13.6.2.1 全般

local_variable_declarationは、1 つ以上のローカル変数を宣言します。

local_variable_declaration
    : implicitly_typed_local_variable_declaration
    | explicitly_typed_local_variable_declaration
    | explicitly_typed_ref_local_variable_declaration
    ;

暗黙的に型指定された宣言には、コンテキスト キーワード (§6.4.4) var 含まれるため、次のように解決される 3 つのカテゴリ間で構文的なあいまいさが生じる可能性があります。

  • スコープに var という名前の型がなく、入力が implicitly_typed_local_variable_declaration 一致する場合は選択されます。
  • それ以外の場合、 var という名前の型がスコープ内にある場合、 implicitly_typed_local_variable_declaration は一致するとは見なされません。

local_variable_declaration内では、各変数はdeclaratorによって導入されます。これは、暗黙的に型指定され、明示的に型指定され、ローカル変数を参照するためのimplicitly_typed_local_variable_declaratorexplicitly_typed_local_variable_declarator、またはref_local_variable_declaratorのいずれかです。 宣言子は、導入された変数の名前 (identifier) と初期値 (存在する場合) を定義します。

宣言に複数の宣言子がある場合は、初期化式を含め、左から右 (§9.4.4.5) で処理されます。

: local_variable_declarationfor_initializer (§13.9.4) または resource_acquisition (§13.14) として発生しない場合、この左から右の順序は、各宣言子が個別の local_variable_declaration内にある場合と同じです。 次に例を示します。

void F()
{
    int x = 1, y, z = x * 2;
}

は以下に匹敵します。

void F()
{
    int x = 1;
    int y;
    int z = x * 2;
}

end note

ローカル変数の値は、 simple_name (§12.8.4) を使用して式で取得されます。 ローカル変数は、値が取得される各場所に確実に割り当てられます (§9.4)。 local_variable_declarationによって導入された各ローカル変数は、に割り当てられていない (§9.4.3) です。 宣言子に初期化式がある場合、導入されたローカル変数は宣言子の末尾に assigned として分類されます (§9.4.4.5)。

local_variable_declarationによって導入されたローカル変数のスコープは、次のように定義されます (§7.7)。

  • 宣言が for_initializer として発生する場合、スコープは for_initializerfor_conditionfor_iterator、および embedded_statement (§13.9.4) です。
  • 宣言が resource_acquisition として発生する場合、スコープは、 using_statement の意味的に等価な拡張の最も外側のブロックです (§13.14)。
  • それ以外の場合、スコープは宣言が行われるブロックです。

宣言子の前のテキスト位置、または宣言子内の初期化式内で、ローカル変数を名前で参照するとエラーになります。 ローカル変数のスコープ内では、同じ名前の別のローカル変数、ローカル関数、または定数を宣言するのはコンパイル時エラーです。

ref ローカル変数の ref-safe-context (§9.7.2) は、初期化 variable_referenceの ref-safe コンテキストです。 ref 以外のローカル変数の ref-safe コンテキストは、 declaration-blockです。

13.6.2.2 暗黙的に型指定されたローカル変数宣言

implicitly_typed_local_variable_declaration
    : 'var' implicitly_typed_local_variable_declarator
    | ref_kind 'var' ref_local_variable_declarator
    ;

implicitly_typed_local_variable_declarator
    : identifier '=' expression
    ;

implicitly_typed_local_variable_declarationでは、1 つのローカル変数 identifier が導入されます。 またはvariable_referenceには、コンパイル時の型Tを指定する必要があります。 最初の代替方法では、expressionの初期値を持つ変数を宣言します。Tが null 非許容参照型の場合、その型はT?。それ以外の場合は、その型がT。 2 番目の代替方法は、初期値が ref variable_reference の ref 変数を宣言します。Tが null 非許容参照型の場合、その型はref T?。それ以外の場合、その型はref T。 (ref_kind は、 §15.6.1 で説明されています。

例:

var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
ref var j = ref i;
ref readonly var k = ref i;

上記の暗黙的に型指定されたローカル変数宣言は、次の明示的に型指定された宣言と正確に等価です。

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>();
ref int j = ref i;
ref readonly int k = ref i;

暗黙的に型指定されたローカル変数の宣言が正しくありません。

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 v itself

end の例

13.6.2.3 明示的に型指定されたローカル変数宣言

explicitly_typed_local_variable_declaration
    : type explicitly_typed_local_variable_declarators
    ;

explicitly_typed_local_variable_declarators
    : explicitly_typed_local_variable_declarator
      (',' explicitly_typed_local_variable_declarator)*
    ;

explicitly_typed_local_variable_declarator
    : identifier ('=' local_variable_initializer)?
    ;

local_variable_initializer
    : expression
    | array_initializer
    ;

explicity_typed_local_variable_declarationでは、指定した type を持つ 1 つ以上のローカル変数が導入されます。

local_variable_initializerが存在する場合、その型は単純な代入 (§12.21.2) または配列初期化 (§17.7) の規則に従って適切であり、その値は変数の初期値として割り当てられます。

13.6.2.4 明示的に型指定された ref ローカル変数宣言

explicitly_typed_ref_local_variable_declaration
    : ref_kind type ref_local_variable_declarators
    ;

ref_local_variable_declarators
    : ref_local_variable_declarator (',' ref_local_variable_declarator)*
    ;

ref_local_variable_declarator
    : identifier '=' 'ref' variable_reference
    ;

初期化 variable_reference は型 type を持ち、 ref 代入 (§12.21.3) の場合と同じ要件を満たす必要があります。

ref_kindref readonlyの場合、宣言されているidentifierは読み取り専用として扱われる変数への参照です。 それ以外の場合、 ref_kindrefされている場合、宣言されている identifier は書き込み可能な変数への参照になります。

method_modifier asyncで宣言されたメソッド内、または反復子 (§15.14 内で ref ローカル変数またはref struct型の変数を宣言するのはコンパイル時エラーです。

13.6.3 ローカル定数宣言

local_constant_declarationは、1 つ以上のローカル定数を宣言します。

local_constant_declaration
    : 'const' type constant_declarators
    ;

constant_declarators
    : constant_declarator (',' constant_declarator)*
    ;

constant_declarator
    : identifier '=' constant_expression
    ;

local_constant_declarationは、宣言によって導入された定数の型を指定します。 型の後に constant_declaratorの一覧が続き、それぞれに新しい定数が導入されます。 constant_declaratorは、定数に名前を付けるidentifierの後に "=" トークンが続き、定数の値を指定するconstant_expression (§12.23) で構成されます。

ローカル定数宣言の type および constant_expression は、定数メンバー宣言 (§15.4) と同じ規則に従う必要があります。

ローカル定数の値は、 simple_name (§12.8.4) を使用して式で取得されます。

ローカル定数のスコープは、宣言が行われるブロックです。 constant_declaratorの末尾の前にあるテキスト位置のローカル定数を参照するとエラーになります。

複数の定数を宣言するローカル定数宣言は、同じ型の 1 つの定数の複数の宣言と同じです。

13.6.4 ローカル関数の宣言

local_function_declarationはローカル関数を宣言します。

local_function_declaration
    : local_function_modifier* return_type local_function_header
      local_function_body
    | ref_local_function_modifier* ref_kind ref_return_type
      local_function_header ref_local_function_body
    ;

local_function_header
    : identifier '(' parameter_list? ')'
    | identifier type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

local_function_modifier
    : ref_local_function_modifier
    | 'async'
    ;

ref_local_function_modifier
    : 'static'
    | unsafe_modifier   // unsafe code support
    ;

local_function_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    ;

ref_local_function_body
    : block
    | '=>' 'ref' variable_reference ';'
    ;

文法上の注意: null_conditional_invocation_expression式の両方が適用される場合、local_function_bodyを認識する場合前者が選択されます。 (§15.6.1)

: ローカル関数には、反復子メソッドと非同期メソッドの 2 つの一般的なユース ケースがあります。 反復子メソッドの場合、例外が検出されるのは、返されたシーケンスを列挙するコードを呼び出した場合のみです。 非同期メソッドでは、返されたタスクが待機されている場合にのみ例外が観察されます。 次の例では、ローカル関数を使用し、反復子の実装からパラメーター検証を分ける動作を確認できます。

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if (start < 'a' || start > 'z')
    {
        throw new ArgumentOutOfRangeException(paramName: nameof(start),
            message: "start must be a letter");
    }
    if (end < 'a' || end > 'z')
    {
        throw new ArgumentOutOfRangeException(paramName: nameof(end),
            message: "end must be a letter");
    }
    if (end <= start)
    {
        throw new ArgumentException(
            $"{nameof(end)} must be greater than {nameof(start)}");
    }
    return AlphabetSubsetImplementation();

    IEnumerable<char> AlphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
        {
            yield return c;
        }
    }
}

end の例

以下で特に指定しない限り、すべての文法要素のセマンティクスは、メソッドではなくローカル関数のコンテキストで読み取 method_declaration (§15.6.1) の場合と同じです。

local_function_declarationidentifierは、外側のローカル変数宣言スペースを含め、宣言されたブロック スコープ内で一意である必要があります。 この結果の 1 つは、オーバーロードされた local_function_declarationが許可されないことです。

local_function_declarationには、1 つのasync (§15.15) 修飾子と 1 つのunsafe (§23.1) 修飾子を含めることができます。 宣言に async 修飾子が含まれている場合、戻り値の型は void または «TaskType» 型 (§15.15.1) になります。 宣言に static 修飾子が含まれている場合、関数は 静的ローカル関数です。それ以外の場合は、 非静的ローカル関数です。 これは、 type_parameter_list または parameter_listattributes が含まれる場合のコンパイル時エラーです。 ローカル関数が安全でないコンテキスト (§23.2) で宣言されている場合、ローカル関数の宣言に unsafe 修飾子が含まれていない場合でも、ローカル関数に安全でないコードが含まれる可能性があります。

ローカル関数はブロック スコープで宣言されます。 非静的ローカル関数は、外側のスコープから変数をキャプチャすることができますが、静的ローカル関数は含めないでください (そのため、外側のローカル、パラメーター、非静的ローカル関数、または thisにアクセスすることはできません)。 キャプチャされた変数が非静的ローカル関数の本体によって読み取られたが、関数の各呼び出しの前に確実に割り当てられない場合は、コンパイル時エラーです。 コンパイラは、どの変数が確実に戻り時に割り当てられるかを決定します (§9.4.4.33)。

thisの型が構造体型の場合、ローカル関数の本体がthisにアクセスするためのコンパイル時エラーです。 これは、アクセスが明示的 (this.xと同様) か暗黙的か (xxが構造体のインスタンス メンバーである場合) に当てはまります。 この規則では、このようなアクセスのみが禁止され、メンバー参照によって構造体のメンバーが作成されるかどうかには影響しません。

goto ステートメント、break ステートメント、またはターゲットがローカル関数の本体の外部にあるcontinueステートメントを含むのは、ローカル関数の本体のコンパイル時エラーです。

: this および goto に関する上記の規則は、 §12.19.3 の匿名関数の規則を反映しています。 end note

ローカル関数は、宣言の前に字句ポイントから呼び出される場合があります。 ただし、ローカル関数で使用される変数の宣言 (§7.7) の前に、関数を構文的に宣言するのはコンパイル時エラーです。

ローカル関数が、外側のローカル変数宣言空間で宣言されたものと同じ名前のパラメーター、型パラメーター、またはローカル変数を宣言するのはコンパイル時エラーです。

ローカル関数本体には常に到達可能です。 ローカル関数宣言の開始点に到達可能な場合、ローカル関数宣言のエンドポイントに到達できます。

: 次の例では、Lの始点に到達できない場合でも、Lの本文に到達できます。 Lの始点に到達できないため、Lのエンドポイントに続くステートメントに到達できません。

class C
{
    int M()
    {
        L();
        return 1;

        // Beginning of L is not reachable
        int L()
        {
            // The body of L is reachable
            return 2;
        }
        // Not reachable, because beginning point of L is not reachable
        return 3;
    }
}

つまり、ローカル関数宣言の場所は、包含関数内のステートメントの到達可能性には影響しません。 end の例

ローカル関数の引数の型が dynamic場合、呼び出される関数は実行時ではなくコンパイル時に解決されます。

ローカル関数は、式ツリーでは使用できません。

静的ローカル関数

  • 外側のスコープから静的メンバー、型パラメーター、定数定義、静的ローカル関数を参照できます。
  • 暗黙的なthis参照からthisまたはbaseまたはインスタンス メンバーを参照したり、外側のスコープからローカル変数、パラメーター、または非静的ローカル関数を参照したりしないでください。 ただし、これらのすべてが nameof() 式で許可されます。

13.7 式ステートメント

expression_statementは、特定の式を評価します。 式によって計算された値 (存在する場合) は破棄されます。

expression_statement
    : statement_expression ';'
    ;

statement_expression
    : null_conditional_invocation_expression
    | invocation_expression
    | object_creation_expression
    | assignment
    | post_increment_expression
    | post_decrement_expression
    | pre_increment_expression
    | pre_decrement_expression
    | await_expression
    ;

すべての式がステートメントとして許可されているわけではありません。

: 特に、単に値を計算する (破棄される) x + yx == 1などの式は、ステートメントとして許可されません。 end note

expression_statementの実行は、含まれている式を評価し、制御をexpression_statementのエンドポイントに転送します。 expression_statementに到達できる場合、expression_statementの終点に到達できます。

13.8 Selection ステートメント

13.8.1 全般

選択ステートメントは、いくつかの式の値に基づいて、実行できるいくつかのステートメントの 1 つを選択します。

selection_statement
    : if_statement
    | switch_statement
    ;

13.8.2 if ステートメント

if ステートメントは、ブール式の値に基づいて実行するステートメントを選択します。

if_statement
    : 'if' '(' boolean_expression ')' embedded_statement
    | 'if' '(' boolean_expression ')' embedded_statement
      'else' embedded_statement
    ;

else部分は、構文で許可されている構文の最も近い前のifに関連付けられています。

: したがって、フォームの if ステートメント

if (x) if (y) F(); else G();

上記の式は、次の式と同じです。

if (x)
{
    if (y)
    {
        F();
    }
    else
    {
        G();
    }
}

end の例

if ステートメントは次のように実行されます。

  • boolean_expression (§12.24) が評価されます。
  • ブール式が trueを生成する場合、コントロールは最初の埋め込みステートメントに転送されます。 コントロールがそのステートメントの終点に達した場合、制御は if ステートメントのエンドポイントに転送されます。
  • ブール式が false を生成し、 else 部分が存在する場合は、2 番目の埋め込みステートメントに制御が転送されます。 コントロールがそのステートメントの終点に達した場合、制御は if ステートメントのエンドポイントに転送されます。
  • ブール式が false を生成し、 else 部分が存在しない場合は、 if ステートメントのエンドポイントに制御が転送されます。

if ステートメントの最初の埋め込みステートメントは、if ステートメントに到達可能で、ブール式に定数値falseがない場合に到達できます。

if ステートメントの 2 番目の埋め込みステートメント (存在する場合) は、if ステートメントに到達可能で、ブール式に定数値trueがない場合に到達可能です。

if ステートメントのエンドポイントは、その埋め込みステートメントの少なくとも 1 つのエンドポイントに到達可能な場合に到達できます。 さらに、if ステートメントに到達可能で、ブール式に定数値がtrueされていない場合、else部分のないif ステートメントのエンドポイントに到達できます。

13.8.3 switch ステートメント

switch ステートメントは、switch 式の値に対応するスイッチ ラベルが関連付けられたステートメント リストを実行するために選択します。

switch_statement
    : 'switch' '(' expression ')' switch_block
    ;

switch_block
    : '{' switch_section* '}'
    ;

switch_section
    : switch_label+ statement_list
    ;

switch_label
    : 'case' pattern case_guard?  ':'
    | 'default' ':'
    ;

case_guard
    : 'when' expression
    ;

switch_statementは、キーワード switchの後に、かっこで囲まれた式 (switch 式と呼ばれます)、その後にswitch_blockで構成されます。 switch_blockは、0 個以上のswitch_sectionで構成され、中かっこで囲まれています。 各 switch_section は、1 つ以上の switch_labelの後に statement_list (§13.3.2) で構成されます。 caseを含む各switch_labelには、switch 式の値がテストされるパターン (§11) が関連付けられています。 case_guardが存在する場合、その式はbool型に暗黙的に変換でき、その式はケースが満たされていると見なされる追加の条件として評価されます。

switch ステートメントのgoverning 型は、switch 式によって確立されます。

  • switch 式の型が sbytebyteshortushortintuintlongulong charboolstring、または enum_typeである場合、またはこれらの型のいずれかに対応する null 許容値型である場合は、 switch ステートメントの管理型になります。
  • それ以外の場合、switch 式の型から次のいずれかの可能な制御型に対して、ユーザー定義の暗黙的な変換が 1 つだけ存在する場合: sbytebyteshortushortintuintlongulongcharstring、またはこれらの型のいずれかに対応する null 許容値型。変換後の型は、 switch ステートメントの制御型です。
  • それ以外の場合、 switch ステートメントの制御型は switch 式の型です。 このような型が存在しない場合はエラーです。

switch ステートメントには、最大で 1 つのdefaultラベルを含めることができます。

スイッチ ラベルのパターンが入力式の型 適用 (§11.2.1) でない場合は、エラーになります。

スイッチ ラベルのパターンが (§11.3) によって、ケース ガードを持たない、またはケース ガードが値 true の定数式である switch ステートメントの以前のスイッチ ラベルのパターンセットによってエラーになります。

例:

switch (shape)
{
    case var x:
        break;
    case var _: // error: pattern subsumed, as previous case always matches
        break;
    default:
        break;  // warning: unreachable, all possible values already handled.
}

end の例

switch ステートメントは次のように実行されます。

  • switch 式が評価され、管理型に変換されます。
  • 制御は、変換されたスイッチ式の値に従って転送されます。
    • switch 式の値に一致し、ガード式が存在しないか true と評価される同じswitch ステートメント内の一連のcaseラベルの構文上の最初のパターンにより、一致したcase ラベルの後のステートメント リストに制御が転送されます。
    • それ以外の場合、 default ラベルが存在する場合は、 default ラベルの後のステートメント リストに制御が転送されます。
    • それ以外の場合、制御は switch ステートメントのエンドポイントに転送されます。

: 実行時にパターンが一致する順序は定義されていません。 コンパイラは、パターンを順不同に一致させ、既に一致したパターンの結果を再利用して他のパターンの一致の結果を計算することが許可されます (必須ではありません)。 ただし、コンパイラは、式に一致し、guard 句が存在しないか、 trueに評価される構文上の最初のパターンを決定する必要があります。 end note

switch セクションのステートメント リストのエンドポイントに到達できない場合は、コンパイル時エラーが発生します。 これは"フォールスルーなし" ルールと呼ばれます。

: 例

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    default:
        CaseOthers();
        break;
}

は有効です。スイッチ セクションに到達可能なエンドポイントがないためです。 C および C++ とは異なり、switch セクションの実行は、次の switch セクションに "フォールスルー" することは許可されていません。この例では、

switch (i)
{
    case 0:
        CaseZero();
    case 1:
        CaseZeroOrOne();
    default:
        CaseAny();
}

の場合、コンパイル時エラーが発生します。 switch セクションの実行の後に別の switch セクションを実行する場合は、明示的な goto case または goto default ステートメントを使用する必要があります。

switch (i)
{
    case 0:
        CaseZero();
        goto case 1;
    case 1:
        CaseZeroOrOne();
        goto default;
    default:
        CaseAny();
        break;
}

end の例

switch_sectionでは複数のラベルを使用できます。

: 例

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    case 2:
    default:
        CaseTwo();
        break;
}

は有効です。 ラベル case 2:default: は同じ switch_sectionの一部であるため、この例は "フォールスルーなし" ルールに違反しません。

end の例

: "フォール スルーなし" ルールは、 break ステートメントが誤って省略された場合に C および C++ で発生するバグの一般的なクラスを防ぎます。 たとえば、上記の switch ステートメントのセクションは、ステートメントの動作に影響を与えずに元に戻すことができます。

switch (i)
{
    default:
        CaseAny();
        break;
    case 1:
        CaseZeroOrOne();
        goto default;
    case 0:
        CaseZero();
        goto case 1;
}

end note

: switch セクションのステートメント・リストは、通常、 breakgoto case、または goto default ステートメントで終わりますが、ステートメント・リストの終点を到達不能にするコンストラクトは許可されます。 たとえば、ブール式trueによって制御されるwhile ステートメントは、そのエンドポイントに到達しないことがわかっているとします。 同様に、 throw ステートメントまたは return ステートメントは常に制御を他の場所に転送し、そのエンドポイントに到達することはありません。 したがって、次の例は有効です。

switch (i)
{
     case 0:
         while (true)
         {
             F();
         }
     case 1:
         throw new ArgumentException();
     case 2:
         return;
}

end note

: switch ステートメントの管理型は、 string型にすることができます。 次に例を示します。

void DoCommand(string command)
{
    switch (command.ToLower())
    {
        case "run":
            DoRun();
            break;
        case "save":
            DoSave();
            break;
        case "quit":
            DoQuit();
            break;
        default:
            InvalidCommand(command);
            break;
    }
}

end の例

: 文字列等値演算子 (§12.12.8) と同様に、 switch ステートメントでは大文字と小文字が区別され、switch 式の文字列が case ラベル定数と完全に一致する場合にのみ、特定の switch セクションが実行されます。 end note switch ステートメントの制御型が string または null 許容値型の場合、 null 値は case ラベル定数として許可されます。

switch_blockstatement_listには、宣言ステートメント (§13.6) を含める場合があります。 switch ブロックで宣言されているローカル変数または定数のスコープは、switch ブロックです。

次のうち少なくとも 1 つが当てはまる場合、スイッチ ラベルに到達できます。

  • switch 式は定数値であり、
    • ラベルは、その値パターンが一致する (§11.2.1) を持つcaseであり、ラベルのガード値が false の定数式がないか、または
    • これは default ラベルであり、スイッチ セクションには、その値に一致するパターンを持ち、ガードが存在しないか、値が true の定数式を持つケース ラベルが含まれています。
  • switch 式は定数値ではなく、
    • ラベルは、ガードのない case 、または値が定数 false ではないガードを持つです。
    • それは default ラベルであり、
      • ガードを持たない、または値が定数 true のガードを持つ switch ステートメントのケースの中で出現するパターンのセットは、スイッチの制御の種類 (§11.4) ではありません。
      • スイッチ 制御型は null 許容型であり、ガードを持たない、または値が true のガードを持つ switch ステートメントのケースの中で出現するパターンのセットには、 null値と一致するパターンが含まれていません。
  • スイッチ ラベルは、到達可能な goto case または goto default ステートメントによって参照されます。

特定の switch セクションのステートメント リストは、 switch ステートメントに到達可能で、switch セクションに到達可能なスイッチ ラベルが含まれている場合に到達可能です。

switch ステートメントが到達可能であり、次の少なくとも 1 つが当てはまる場合、 switch ステートメントのエンドポイントに到達できます。

  • switch ステートメントには、switch ステートメントを終了する到達可能なbreak ステートメントが含まれています。
  • defaultラベルが存在せず、いずれか
    • switch 式は定数以外の値であり、スイッチ ステートメントのケースの中で、ガードを持たないか、値が定数 true のガードを持つパターンのセットは、スイッチ の制御型に対して 完全ではありません (§11.4)。
    • switch 式は null 許容型の非定数値であり、ガードを持たない、または定数が true のガードを持つ switch ステートメントの場合は、 null値と一致するパターンはありません。
    • switch 式は定数値であり、ガードがない case ラベルも、そのガードが定数 true の場合は、その値と一致しません。

: 次のコードは、 when 句の簡潔な使用を示しています。

static object CreateShape(string shapeDescription)
{
   switch (shapeDescription)
   {
        case "circle":
            return new Circle(2);
        …
        case var o when string.IsNullOrWhiteSpace(o):
            return null;
        default:
            return "invalid shape description";
    }
}

var ケースは、 null、空の文字列、または空白のみを含む任意の文字列と一致します。 end の例

13.9 Iteration ステートメント

13.9.1 全般

反復ステートメントは、埋め込みステートメントを繰り返し実行します。

iteration_statement
    : while_statement
    | do_statement
    | for_statement
    | foreach_statement
    ;

13.9.2 while ステートメント

while ステートメントは、埋め込みステートメントを 0 回以上条件付きで実行します。

while_statement
    : 'while' '(' boolean_expression ')' embedded_statement
    ;

while ステートメントは次のように実行されます。

  • boolean_expression (§12.24) が評価されます。
  • ブール式が trueを生成する場合、コントロールは埋め込みステートメントに転送されます。 コントロールが ( continue ステートメントの実行から) 埋め込みステートメントのエンドポイントに到達した場合、制御は while ステートメントの先頭に転送されます。
  • ブール式が falseを生成する場合は、 while ステートメントのエンドポイントに制御が転送されます。

while ステートメントの埋め込みステートメント内で、 breakステートメント (§13.10.2) を使用して、while ステートメントのエンドポイントに制御を転送できます (したがって、埋め込みステートメントの反復処理を終了します)、continueステートメント (§13.10.3) を使用して、埋め込みステートメントのエンドポイントに制御を転送できます (したがって、whileステートメントのもう 1 つの反復処理を実行します)。

while ステートメントの埋め込みステートメントに到達できるのは、while ステートメントに到達可能で、ブール式に定数値falseがない場合です。

次の少なくとも 1 つが当てはまる場合、 while ステートメントのエンドポイントに到達できます。

  • while ステートメントには、while ステートメントを終了する到達可能なbreak ステートメントが含まれています。
  • while ステートメントに到達可能であり、ブール式には定数値trueがありません。

13.9.3 do ステートメント

do ステートメントは、埋め込みステートメントを 1 回以上条件付きで実行します。

do_statement
    : 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
    ;

do ステートメントは次のように実行されます。

  • コントロールは埋め込みステートメントに転送されます。
  • コントロールが ( continue ステートメントの実行から) 埋め込みステートメントのエンドポイントに到達すると、 boolean_expression (§12.24) が評価されます。 ブール式が trueを生成する場合は、 do ステートメントの先頭に制御が転送されます。 それ以外の場合、制御は do ステートメントのエンドポイントに転送されます。

do ステートメントの埋め込みステートメント内で、 breakステートメント (§13.10.2) を使用して、do ステートメントのエンドポイントに制御を転送できます (したがって、埋め込みステートメントの反復処理を終了します)、continueステートメント (§13.10.3) を使用して、埋め込みステートメントのエンドポイントに制御を転送できます (したがって、doステートメントのもう 1 つの反復処理を実行します)。

do ステートメントに到達可能な場合、do ステートメントの埋め込みステートメントに到達できます。

次の少なくとも 1 つが当てはまる場合、 do ステートメントのエンドポイントに到達できます。

  • do ステートメントには、do ステートメントを終了する到達可能なbreak ステートメントが含まれています。
  • 埋め込みステートメントの終点に到達可能であり、ブール式には定数値 trueがありません。

13.9.4 for ステートメント

for ステートメントは初期化式のシーケンスを評価し、条件が true の場合は、埋め込みステートメントを繰り返し実行し、反復式のシーケンスを評価します。

for_statement
    : 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
      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)*
    ;

for_initializerが存在する場合は、コンマで区切られたlocal_variable_declaration (§13.6.2) またはstatement_expressionのリスト (§13.7) で構成されます。 for_initializerによって宣言されるローカル変数のスコープは、for_initializerfor_conditionfor_iterator、およびembedded_statementです。

for_conditionが存在する場合は、boolean_expression (§12.24)。

for_iterator (存在する場合) は、コンマで区切られたstatement_expression (§13.7) のリストで構成されます。

for ステートメントは次のように実行されます。

  • for_initializerが存在する場合、変数初期化子またはステートメント式は、書き込まれた順序で実行されます。 この手順は 1 回だけ実行されます。
  • for_conditionが存在する場合は評価されます。
  • for_conditionが存在しない場合、または評価によってtrueが生成された場合、制御は埋め込みステートメントに転送されます。 コントロールが ( continue ステートメントの実行から) 埋め込みステートメントのエンドポイントに到達すると、 for_iteratorの式 (存在する場合) が順番に評価され、上の手順の for_condition の評価から始めて、別の反復が実行されます。
  • for_conditionが存在し、評価によってfalseが生成された場合、制御は for ステートメントのエンドポイントに転送されます。

for ステートメントの埋め込みステートメント内では、break ステートメント (§13.10.2) を使用して、for ステートメントのエンドポイントに制御を転送できます (したがって、埋め込みステートメントの反復処理を終了します)。 およびcontinueステートメント (§13.10.3) を使用して、埋め込みステートメントのエンドポイントに制御を転送できます (したがって、for_iteratorを実行し、for ステートメントの別の反復を実行します。 for_condition以降) を選択します。

次のいずれかに該当する場合、 for ステートメントの埋め込みステートメントに到達できます。

  • for ステートメントに到達でき、for_conditionはありません。
  • for ステートメントに到達でき、for_conditionが存在し、定数値false

次の少なくとも 1 つが当てはまる場合、 for ステートメントのエンドポイントに到達できます。

  • for ステートメントには、for ステートメントを終了する到達可能なbreak ステートメントが含まれています。
  • for ステートメントに到達でき、for_conditionが存在し、定数値true

13.9.5 foreach ステートメント

foreach ステートメントはコレクションの要素を列挙し、コレクションの各要素に対して埋め込みステートメントを実行します。

foreach_statement
    : 'foreach' '(' ref_kind? local_variable_type identifier 'in' 
      expression ')' embedded_statement
    ;

foreach ステートメントの local_variable_typeidentifier は、ステートメントの iteration 変数 を宣言します。 var識別子がlocal_variable_typeとして指定され、varという名前の型がスコープ内に存在しない場合、反復変数は単純に型指定された反復変数と呼ばれ、その型は次に示すように、foreach ステートメントの要素型と見なされます。

foreach_statementrefreadonlyの両方または両方が含まれている場合、反復変数は読み取り専用として扱われる変数を表します。 それ以外の場合、foreach_statementreadonlyのないrefが含まれている場合、反復変数は書き込み可能な変数を表します。

反復変数は、埋め込みステートメントに拡張されるスコープを持つローカル変数に対応します。 foreach ステートメントの実行中、イテレーション変数は、イテレーションが現在実行されているコレクション要素を表します。 反復変数が読み取り専用変数を示す場合、埋め込みステートメントが (代入演算子または ++ 演算子と -- 演算子を使用して) 変更しようとした場合、または参照パラメーターまたは出力パラメーターとして渡そうとすると、コンパイル時エラーが発生します。

次の例では、簡潔にするために、 IEnumerableIEnumeratorIEnumerable<T> 、および IEnumerator<T>System.Collections および System.Collections.Genericの名前空間内の対応する型を参照します。

foreach ステートメントのコンパイル時の処理では、まず、式のコレクション型enumerator 型および iteration 型が決定されます。 この決定は次のように進みます。

  • expressionの型Xが配列型の場合、XからIEnumerableインターフェイスへの暗黙的な参照変換があります(System.Arrayはこのインターフェイスを実装するため)。 コレクション型は IEnumerable インターフェイス、列挙子の型は IEnumerator インターフェイス、反復処理の型は配列型の要素型 X
  • expression の型Xdynamic場合、expressionからIEnumerableインターフェイス (§10.2.10) への暗黙的な変換があります。 コレクション型は IEnumerable インターフェイスであり、列挙子の型は IEnumerator インターフェイスです。 var識別子がlocal_variable_typeとして指定されている場合、反復型はdynamicされ、それ以外の場合はobject
  • それ以外の場合は、型 X に適切な GetEnumerator メソッドがあるかどうかを判断します。
    • 識別子GetEnumeratorを持ち、型引数を持たない型Xに対してメンバー参照を実行します。 メンバー参照で一致が生成されない場合、またはあいまいさが生成される場合、またはメソッド グループではない一致が生成される場合は、次に説明するように列挙可能なインターフェイスを確認します。 メンバー参照でメソッド グループ以外の何かを生成する場合、または一致するものがない場合は、警告を発行することをお勧めします。
    • 結果のメソッド グループと空の引数リストを使用して、オーバーロードの解決を実行します。 オーバーロードの解決によって該当するメソッドが存在しない場合、あいまいになる場合、または単一の最適なメソッドになるが、そのメソッドが静的であるかパブリックでない場合は、次に説明するように列挙可能なインターフェイスを確認します。 オーバーロードの解決で明確なパブリック インスタンス メソッド以外の何かを生成する場合、または該当するメソッドが生成されない場合は、警告を発行することをお勧めします。
    • GetEnumerator メソッドの戻り値の型Eがクラス、構造体、またはインターフェイス型でない場合は、エラーが生成され、それ以上の手順は実行されません。
    • メンバー参照は、識別子Currentを持つEに対して実行され、型引数はありません。 メンバー参照で一致が生成されない場合、結果がエラーであるか、読み取りを許可するパブリック インスタンス プロパティ以外の結果である場合は、エラーが生成され、それ以上の手順は実行されません。
    • メンバー参照は、識別子MoveNextを持つEに対して実行され、型引数はありません。 メンバー参照で一致が生成されない場合、結果がエラーである場合、または結果がメソッド グループを除くものである場合、エラーが生成され、それ以上の手順は実行されません。
    • オーバーロードの解決は、空の引数リストを使用してメソッド グループに対して実行されます。 オーバーロードの解決によって該当するメソッドが得られない場合、あいまいさが生じるか、1 つの最適なメソッドになりますが、そのメソッドが静的であるかパブリックでないか、戻り値の型が boolされていない場合、エラーが生成され、それ以上の手順は実行されません。
    • コレクション型が Xされ、列挙子の型が Eされ、反復処理の型が Current プロパティの型です。 Current プロパティには、ref修飾子を含めることができます。その場合、返される式は、必要に応じて読み取り専用のvariable_reference (§9.5) です。
  • それ以外の場合は、列挙可能なインターフェイスを確認します。
    • XからIEnumerable<Tᵢ>への暗黙的な変換が存在するすべての型TᵢTdynamicされないように一意の型Tがあり、他のすべてのTᵢに対してIEnumerable<T>からIEnumerable<Tᵢ>への暗黙的な変換がある場合、コレクション型はインターフェイス IEnumerable<T>、列挙子の型はインターフェイス IEnumerator<T>、反復処理の種類はT
    • それ以外の場合、このような型 Tが複数存在する場合は、エラーが生成され、それ以上の手順は実行されません。
    • それ以外の場合、 X から System.Collections.IEnumerable インターフェイスへの暗黙的な変換がある場合、コレクション型はこのインターフェイス、列挙子の型はインターフェイス System.Collections.IEnumerator、反復処理の型は object
    • それ以外の場合は、エラーが生成され、それ以上の手順は実行されません。

上記の手順が成功した場合、コレクション型 C、列挙子の型 E 、反復型の Tref T、または ref readonly Tを明確に生成します。 フォームの foreach ステートメント

foreach (V v in x) «embedded_statement»

は次のようになります。

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

e変数は、式x、埋め込みステートメント、またはプログラムのその他のソース コードに対して表示またはアクセスできません。 v変数は、埋め込みステートメントでは読み取り専用です。 T (反復型) からV (foreach ステートメントのlocal_variable_type) への明示的な変換 (§10.3) がない場合は、エラーが生成され、それ以上の手順は実行されません。

反復変数が参照変数 (§9.7) の場合、フォームの foreach ステートメント

foreach (ref V v in x) «embedded_statement»

は次のようになります。

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            ref V v = ref e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

変数 e は、式 x 、埋め込みステートメント、またはプログラムのその他のソース コードに対して表示またはアクセスできません。 v参照変数は埋め込みステートメントでは読み取り/書き込みですが、vは再割り当てできません (§12.21.3)。 T (反復型) からV (foreach ステートメントのlocal_variable_type) への ID 変換 (§10.2.2) がない場合は、エラーが生成され、それ以上の手順は実行されません。

フォーム foreach (ref readonly V v in x) «embedded_statement»foreachステートメントにも同様の形式がありますが、v参照変数は埋め込みステートメントでref readonlyされるため、再割り当てまたは再割り当てすることはできません。

: x の値が nullの場合、実行時に System.NullReferenceException がスローされます。 end note

実装では、特定の foreach_statement を異なる方法で実装することが許可されます。たとえば、パフォーマンス上の理由から、動作が上記の拡張と一致している限りです。

while ループ内でのvの配置は、embedded_statementで発生する匿名関数によってキャプチャされる方法 (§12.19.6.2) にとって重要です。

例:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null)
    {
        f = () => Console.WriteLine("First value: " + value);
    }
}
f();

展開された形式の vwhile ループの外部で宣言された場合、すべてのイテレーション間で共有され、 for ループの後の値は最終的な値 13になり、 f の呼び出しは出力されます。 代わりに、各イテレーションには独自の変数 vがあるため、最初のイテレーションで f によってキャプチャされたものは、出力される値 7を保持し続けます。 (以前のバージョンの C# が while ループの外部v宣言されていることに注意してください)。

end の例

finally ブロックの本体は、次の手順に従って構築されます。

  • EからSystem.IDisposableインターフェイスへの暗黙的な変換がある場合は、

    • Eが null 非許容値型の場合、finally句はセマンティックに相当する型に展開されます。

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • それ以外の場合、 finally 句は次のセマンティックに相当するものに拡張されます。

      finally
      {
          System.IDisposable d = e as System.IDisposable;
          if (d != null)
          {
              d.Dispose();
          }
      }
      

      ただし、 E が値型または値型にインスタンス化された型パラメーターである場合、 e から System.IDisposable への変換ではボックス化は行われません。

  • それ以外の場合、 E がシール型の場合、 finally 句は空のブロックに展開されます。

    finally {}
    
  • それ以外の場合、 finally 句は次のように展開されます。

    finally
    {
        System.IDisposable d = e as System.IDisposable;
        if (d != null)
        {
            d.Dispose();
        }
    }
    

ローカル変数 d は、どのユーザー コードにも表示されず、アクセスできません。 特に、スコープに finally ブロックが含まれる他の変数と競合することはありません。

foreachが配列の要素を走査する順序は次のとおりです。1 次元配列の場合、インデックス 0 から始まり、インデックス Length – 1で終わるインデックスの順序を増やして要素が走査されます。 多次元配列の場合、要素は、右端の次元のインデックスが最初に増加し、次に次の左の次元、および左側に増加するように走査されます。

: 次の例では、要素の順序で 2 次元配列の各値を出力します。

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($"{elementValue} ");
        }
        Console.WriteLine();
    }
}

生成される出力は次のとおりです。

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

end の例

: 次の例では

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

nの型は、intnumbersの反復型と推定されます。

end の例

13.10 ジャンプ ステートメント

13.10.1 全般

ジャンプ ステートメントは無条件に制御を転送します。

jump_statement
    : break_statement
    | continue_statement
    | goto_statement
    | return_statement
    | throw_statement
    ;

ジャンプ ステートメントが制御を転送する場所は、jump ステートメントの target と呼ばれます。

jump ステートメントがブロック内で発生し、その jump ステートメントのターゲットがそのブロックの外側にある場合、jump ステートメントはブロックexit と言われます。 ジャンプ ステートメントはブロックから制御を転送できますが、制御をブロックに転送することはできません。

ジャンプ ステートメントの実行は、介入する try ステートメントが存在することによって複雑になります。 このような try ステートメントがない場合、ジャンプ ステートメントは、ジャンプ ステートメントからターゲットに無条件に制御を転送します。 このような介在する try ステートメントが存在する場合、実行はより複雑になります。 ジャンプ ステートメントが関連付けられた finally ブロックを持つ 1 つ以上のtry ブロックを終了すると、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、介在するすべてのtryステートメントのfinally ブロックが実行されるまで繰り返されます。

: 次のコード内

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");
    }
}

2 つのtry ステートメントに関連付けられているfinally ブロックは、制御がジャンプ ステートメントのターゲットに転送される前に実行されます。 生成される出力は次のとおりです。

Before break
Innermost finally block
Outermost finally block
After break

end の例

13.10.2 break ステートメント

breakステートメントは、最も近い外側のswitchwhiledofor、またはforeachステートメントを終了します。

break_statement
    : 'break' ';'
    ;

break ステートメントのターゲットは、最も近い外側のswitchwhiledofor、またはforeachステートメントの終点です。 breakステートメントがswitchwhiledofor、またはforeachステートメントで囲まれていない場合は、コンパイル時エラーが発生します。

複数の switchwhiledofor、または foreach ステートメントが相互に入れ子になっている場合、 break ステートメントは最も内側のステートメントにのみ適用されます。 複数の入れ子レベル間で制御を転送するには、 goto ステートメント (§13.10.4) を使用する必要があります。

break ステートメントは、finally ブロック (§13.11) を終了できません。 finally ブロック内でbreakステートメントが発生した場合、break ステートメントのターゲットは同じfinally ブロック内に存在する必要があります。それ以外の場合は、コンパイル時エラーが発生します。

break ステートメントは次のように実行されます。

  • break ステートメントが関連付けられた finally ブロックを持つ 1 つ以上のtry ブロックを終了すると、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、介在するすべてのtryステートメントのfinally ブロックが実行されるまで繰り返されます。
  • 制御は、 break ステートメントのターゲットに転送されます。

break ステートメントは無条件に制御を他の場所に転送するため、break ステートメントのエンドポイントに到達できません。

13.10.3 continue ステートメント

continue ステートメントは、最も近い外側のwhiledofor、またはforeachステートメントの新しい反復処理を開始します。

continue_statement
    : 'continue' ';'
    ;

continue ステートメントのターゲットは、最も近い外側のwhiledofor、またはforeachステートメントの埋め込みステートメントのエンドポイントです。 continueステートメントがwhiledofor、またはforeachステートメントで囲まれていない場合は、コンパイル時エラーが発生します。

複数の whiledofor、または foreach ステートメントが相互に入れ子になっている場合、 continue ステートメントは最も内側のステートメントにのみ適用されます。 複数の入れ子レベル間で制御を転送するには、 goto ステートメント (§13.10.4) を使用する必要があります。

continue ステートメントは、finally ブロック (§13.11) を終了できません。 finally ブロック内でcontinueステートメントが発生した場合、continue ステートメントのターゲットは同じfinally ブロック内に存在する必要があります。それ以外の場合は、コンパイル時エラーが発生します。

continue ステートメントは次のように実行されます。

  • continue ステートメントが関連付けられた finally ブロックを持つ 1 つ以上のtry ブロックを終了すると、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、介在するすべてのtryステートメントのfinally ブロックが実行されるまで繰り返されます。
  • 制御は、 continue ステートメントのターゲットに転送されます。

continue ステートメントは無条件に制御を他の場所に転送するため、continue ステートメントのエンドポイントに到達できません。

13.10.4 goto ステートメント

goto ステートメントは、ラベルでマークされたステートメントに制御を転送します。

goto_statement
    : 'goto' identifier ';'
    | 'goto' 'case' constant_expression ';'
    | 'goto' 'default' ';'
    ;

goto identifier ステートメントのターゲットは、指定されたラベルを持つラベル付きステートメントです。 指定された名前のラベルが現在の関数メンバーに存在しない場合、または goto ステートメントがラベルのスコープ内にない場合は、コンパイル時エラーが発生します。

: この規則では、入れ子になったスコープの制御アウト転送するgotoステートメントを使用できますが、入れ子になったスコープできません。 この例では、

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($"{str} not found");
            continue;
          done:
            Console.WriteLine($"Found {str} at [{row}][{colm}]");
        }
    }
}

goto ステートメントは、入れ子になったスコープから制御を転送するために使用されます。

end note

goto case ステートメントのターゲットは、指定された定数値の定数パターンとガードなしのcase ラベルを含む、すぐに囲む switch ステートメント (§13.8.3) のステートメント リストです。 goto caseステートメントがswitchステートメントで囲まれていない場合、最も近い外側のswitchステートメントにそのようなcaseが含まれていない場合、またはconstant_expressionが最も近い外側のswitchステートメントの制御型に暗黙的に変換できない場合 (§10.2) 場合、コンパイル時エラーが発生します。

goto default ステートメントのターゲットは、default ラベルを含む、すぐに囲むswitch ステートメント (§13.8.3) 内のステートメント リストです。 goto defaultステートメントがswitchステートメントで囲まれていない場合、または最も近い外側のswitchステートメントにdefaultラベルが含まれていない場合は、コンパイル時エラーが発生します。

goto ステートメントは、finally ブロック (§13.11) を終了できません。 goto ステートメントがfinally ブロック内で発生した場合、goto ステートメントのターゲットは同じfinally ブロック内にあるか、それ以外の場合はコンパイル時エラーが発生します。

goto ステートメントは次のように実行されます。

  • goto ステートメントが関連付けられた finally ブロックを持つ 1 つ以上のtry ブロックを終了すると、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、介在するすべてのtryステートメントのfinally ブロックが実行されるまで繰り返されます。
  • 制御は、 goto ステートメントのターゲットに転送されます。

goto ステートメントは無条件に制御を他の場所に転送するため、goto ステートメントのエンドポイントに到達できません。

13.10.5 return ステートメント

return ステートメントは、return ステートメントが出現する関数メンバーの現在の呼び出し元に制御を返し、必要に応じて値またはvariable_reference (§9.5) を返します。

return_statement
    : 'return' ';'
    | 'return' expression ';'
    | 'return' 'ref' variable_reference ';'
    ;

式を持たないreturn_statementreturn-no-valueと呼ばれ、ref expression を含むreturn-by-refと呼ばれ、式のみを含む戻り値ごとに呼び出されます

値による戻り値または returns by-ref (§15.6.1) として宣言されたメソッドからの戻り値なし値を使用するのはコンパイル時エラーです。

returns-no-value または returns-by-value として宣言されたメソッドからの return-by-ref を使用するのはコンパイル時エラーです。

returns-no-value または returns-by-ref として宣言されたメソッドから値による戻り値を使用するのはコンパイル時エラーです。

expressionvariable_referenceでない場合、または ref-safe コンテキストが呼び出し元コンテキスト (§9.7.2) ではない変数への参照である場合は、コンパイル時エラーです。

method_modifier asyncで宣言されたメソッドから ref による戻り値を使用するのはコンパイル時エラーです。

関数メンバーは 値を計算 戻り値によるメソッド (§15.6.11)、プロパティまたはインデクサーの値による戻り値取得アクセサー、またはユーザー定義演算子を持つメソッドであると言われます。 値を返さない関数メンバーは、値を計算せず、有効な戻り値の型 void、プロパティとインデクサーのアクセサーの設定、イベント、インスタンス コンストラクター、静的コンストラクター、ファイナライザーのアクセサーの追加と削除を行うメソッドです。 ref で返される関数メンバーは、値を計算しません。

戻り値の場合、暗黙的な変換 (§10.2) は、 expression を含む関数メンバーの有効な戻り値の型 (§15.6.11) に存在する必要があります。 ref による戻り値の場合、id 変換 (§10.2.2) は、 expression 含む関数メンバーの有効な戻り値の型の間に存在する必要があります。

return ステートメントは、匿名関数式 (§12.19) の本体でも使用でき、それらの関数に対して存在する変換の決定に関与できます (§10.7.1)。

return ステートメントが finally ブロックに表示されるコンパイル時エラーです (§13.11)。

return ステートメントは次のように実行されます。

  • 戻り値の場合、 expression が評価され、その値は暗黙的な変換によって包含関数の有効な戻り値の型に変換されます。 変換の結果は、関数によって生成された結果値になります。 ref による戻り値の場合、 が評価され、結果は変数として分類されます。 外側のメソッドの return-by-ref に readonlyが含まれている場合、結果の変数は読み取り専用になります。
  • return ステートメントが 1 つ以上のtryまたは関連するfinally ブロックを持つcatch ブロックで囲まれている場合、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、外側のすべての try ステートメントのfinally ブロックが実行されるまで繰り返されます。
  • 包含関数が非同期関数でない場合は、制御が結果値 (存在する場合) と共に、包含関数の呼び出し元に返されます。
  • 包含関数が非同期関数の場合、制御は現在の呼び出し元に返され、結果値がある場合は、(§15.15.3 で説明されているように戻りタスクに記録されます。

return ステートメントは無条件に制御を他の場所に転送するため、return ステートメントのエンドポイントに到達できません。

13.10.6 throw ステートメント

throw ステートメントは例外をスローします。

throw_statement
    : 'throw' expression? ';'
    ;

式を含む throw ステートメントは、式を評価することによって生成された例外をスローします。 式は暗黙的に System.Exceptionに変換でき、式を評価した結果はスローされる前に System.Exception に変換されます。 変換の結果が null場合は、代わりに System.NullReferenceException がスローされます。

式のない throw ステートメントは、 catch ブロックでのみ使用できます。その場合、そのステートメントは、その catch ブロックによって現在処理されている例外を再スローします。

throw ステートメントは無条件に制御を他の場所に転送するため、throw ステートメントのエンドポイントに到達できません。

例外がスローされると、例外を処理できる外側の try ステートメントの最初のcatch句に制御が転送されます。 例外がスローされた時点から適切な例外ハンドラーに制御を転送する時点までのプロセスは、 例外伝達と呼ばれます。 例外の伝達は、例外に一致する catch 句が見つかるまで、次の手順を繰り返し評価することで構成されます。 この説明では、 位置 は、最初は例外がスローされる場所です。 この動作は (§21.4) で指定されています。

  • 現在の関数メンバーでは、スロー ポイントを囲む各 try ステートメントが調べされます。 ステートメント Sごとに、最も内側の try ステートメントから始まり、最も外側の try ステートメントで終わると、次の手順が評価されます。

    • Stry ブロックがスロー ポイントを囲み、Sに 1 つ以上のcatch句がある場合、catch句は、例外に適したハンドラーを見つけるために外観順に調べされます。 Tから派生したEの実行時の型が一致と見なされるように、例外の種類T (または実行時に例外の型Tを示す型パラメーター) を指定する最初のcatch句。 句に例外フィルターが含まれている場合、例外オブジェクトが例外変数に割り当てられ、例外フィルターが評価されます。 catch句に例外フィルターが含まれている場合、そのcatch句は、例外フィルターがtrueと評価された場合、一致と見なされます。 一般的な catch (§13.11) 句は、すべての例外の種類に一致すると見なされます。 一致する catch 句がある場合は、その catch 句のブロックに制御を転送することで例外伝達が完了します。
    • それ以外の場合、try ブロックまたは Scatch ブロックがスロー ポイントを囲み、Sfinally ブロックがある場合は、finally ブロックに制御が転送されます。 finally ブロックによって別の例外がスローされた場合、現在の例外の処理は終了されます。 それ以外の場合、制御が finally ブロックの終点に達すると、現在の例外の処理が続行されます。
  • 現在の関数呼び出しで例外ハンドラーが見つからない場合、関数の呼び出しは終了し、次のいずれかが発生します。

  • 例外処理が現在のスレッド内のすべての関数メンバー呼び出しを終了し、スレッドに例外のハンドラーがないことを示す場合、スレッド自体は終了します。 このような終了の影響は、実装によって定義されます。

13.11 try ステートメント

try ステートメントは、ブロックの実行中に発生する例外をキャッチするためのメカニズムを提供します。 さらに、 try ステートメントは、コントロールが try ステートメントを離れると常に実行されるコード ブロックを指定する機能を提供します。

try_statement
    : 'try' block catch_clauses
    | 'try' block catch_clauses? finally_clause
    ;

catch_clauses
    : specific_catch_clause+
    | specific_catch_clause* general_catch_clause
    ;

specific_catch_clause
    : 'catch' exception_specifier exception_filter? block
    | 'catch' exception_filter block
    ;

exception_specifier
    : '(' type identifier? ')'
    ;

exception_filter
    : 'when' '(' boolean_expression ')'
    ;

general_catch_clause
    : 'catch' block
    ;

finally_clause
    : 'finally' block
    ;

try_statementは、キーワード tryの後に block、0 個以上のcatch_clauses、オプションのfinally_clauseで構成されます。 少なくとも 1 つの catch_clause または finally_clauseがあります。

exception_specifierでは、またはその有効な基底クラス (type_parameterの場合) は、System.Exceptionまたは派生する型である必要があります。

catch句で class_typeidentifier の両方を指定すると、指定された名前と型の例外変数が宣言されます。 例外変数は、 specific_catch_clause の宣言空間に導入されます (§7.3)。 exception_filterおよびcatch ブロックの実行中、例外変数は現在処理されている例外を表します。 明確な代入チェックのために、例外変数はスコープ全体で確実に割り当てられていると見なされます。

catch句に例外変数名が含まれている場合を除き、フィルターおよびcatch ブロック内の例外オブジェクトにアクセスすることはできません。

例外の種類も例外変数名も指定しない catch 句は、一般的な catch 句と呼ばれます。 try ステートメントは、一般的なcatch句を 1 つだけ持つだけで、存在する場合は最後の catch 句になります。

: 一部のプログラミング言語では、 System.Exceptionから派生したオブジェクトとして表現できない例外がサポートされている場合がありますが、このような例外は C# コードで生成することはできません。 このような例外をキャッチするには、一般的な catch 句を使用できます。 したがって、一般的な catch 句は、前者が他の言語からの例外もキャッチする可能性があるため、 System.Exception型を指定する句とは意味的に異なります。 end note

例外のハンドラーを見つけるために、 catch 句は字句の順序で調べわれます。 catch句で型を指定しても例外フィルターが指定されていない場合、同じtry ステートメントの後のcatch句でコンパイル時エラーが発生し、その型と同じまたは派生した型が指定されます。

: この制限がないと、 catch 句に到達できない可能性があります。 end note

catch ブロック内では、式のないthrow ステートメント (§13.10.6) を使用して、catch ブロックによってキャッチされた例外を再スローできます。 例外変数に代入すると、再スローされる例外は変更されません。

: 次のコード内

class Test
{
    static void F()
    {
        try
        {
            G();
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception in F: " + e.Message);
            e = new Exception("F");
            throw; // re-throw
        }
    }

    static void G() => throw new Exception("G");

    static void Main()
    {
        try
        {
            F();
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception in Main: " + e.Message);
        }
    }
}

メソッド F 例外をキャッチし、コンソールに診断情報を書き込み、例外変数を変更して、例外を再スローします。 再スローされる例外は元の例外であるため、生成される出力は次のようになります。

Exception in F: G
Exception in Main: G

現在の例外を再スローするのではなく、最初の catch ブロックが e スローされた場合、生成される出力は次のようになります。

Exception in F: G
Exception in Main: F

end の例

finally ブロックから制御を転送するbreakcontinue、またはgotoステートメントのコンパイル時エラーです。 finally ブロックでbreakcontinue、またはgotoステートメントが発生した場合、ステートメントのターゲットは同じfinally ブロック内にあるか、コンパイル時エラーが発生します。

finally ブロックでreturn ステートメントが発生するのはコンパイル時エラーです。

実行が try ステートメントに達すると、制御は try ブロックに転送されます。 例外が伝達されずに制御が try ブロックの終点に達した場合、制御は finally ブロックに転送されます (存在する場合)。 finally ブロックが存在しない場合は、try ステートメントのエンドポイントに制御が転送されます。

例外が伝達された場合、 catch 句がある場合は、その例外に対する最初の一致を求めて字句順に調べされます。 一致する catch 句の検索は、 §13.10.6 で説明されているように、外側のすべてのブロックで続行されます。 例外の種類が任意のexception_specifierと一致し、exception_filterが true の場合、catch句は一致します。 exception_specifierのないcatch句は、例外の種類と一致します。 例外の種類は、exception_specifierが例外の種類または例外の種類の基本型を指定するときに、exception_specifierと一致します。 句に例外フィルターが含まれている場合、例外オブジェクトが例外変数に割り当てられ、例外フィルターが評価されます。

例外が伝達され、一致する catch 句が見つかった場合、制御は最初に一致する catch ブロックに転送されます。 例外が伝達されずに制御が catch ブロックの終点に達した場合、制御は finally ブロックに転送されます (存在する場合)。 finally ブロックが存在しない場合は、try ステートメントのエンドポイントに制御が転送されます。 例外が catch ブロックから伝達された場合は、 finally ブロックに制御が転送されます (存在する場合)。 例外は、次の外側の try ステートメントに反映されます。

例外が伝達され、一致する catch 句が見つからない場合は、 finally ブロックに制御が転送されます (存在する場合)。 例外は、次の外側の try ステートメントに反映されます。

finally ブロックのステートメントは、制御が try ステートメントを離れるときに常に実行されます。 これは、 breakcontinuegoto、または return ステートメントの実行の結果として、または try ステートメントから例外を伝達した結果として、コントロール転送が通常の実行の結果として発生するかどうかに当てはまります。 例外が伝達されずに制御が finally ブロックの終点に達すると、制御は try ステートメントのエンドポイントに転送されます。

finally ブロックの実行中に例外がスローされ、同じfinally ブロック内でキャッチされない場合、例外は次に囲むtryステートメントに反映されます。 別の例外が伝達中であった場合、その例外は失われます。 例外を伝達するプロセスについては、 throw ステートメント (§13.10.6) の説明で詳しく説明します。

: 次のコード内

public class Test
{
    static void Main()
    {
        try
        {
            Method();
        }
        catch (Exception ex) when (ExceptionFilter(ex))
        {
            Console.WriteLine("Catch");
        }

        bool ExceptionFilter(Exception ex)
        {
            Console.WriteLine("Filter");
            return true;
        }
    }

    static void Method()
    {
        try
        {
            throw new ArgumentException();
        }
        finally
        {
            Console.WriteLine("Finally");
        }
    }
}

メソッド Method 例外をスローします。 最初のアクションは、外側の catch 句を調べて、 例外フィルターを実行することです。 次に、Methodfinally句は、外側の一致する catch 句に制御が転送される前に実行されます。 結果の出力は次のとおりです。

Filter
Finally
Catch

end の例

try ステートメントに到達可能な場合、try ステートメントのtry ブロックに到達できます。

try ステートメントに到達可能な場合、try ステートメントのcatch ブロックに到達できます。

try ステートメントに到達可能な場合、try ステートメントのfinally ブロックに到達できます。

次の両方に該当する場合、 try ステートメントのエンドポイントに到達できます。

  • try ブロックの終点に到達可能であるか、少なくとも 1 つのcatch ブロックの終点に到達可能です。
  • finally ブロックが存在する場合、finally ブロックの終点に到達できます。

13.12 チェックされたステートメントとチェックされていないステートメント

checkedステートメントとunchecked ステートメントは、整数型の算術演算と変換のオーバーフロー チェック コンテキストを制御するために使用されます。

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

checked ステートメントを使用すると、block 内のすべての式がチェック コンテキストで評価され、unchecked ステートメントによって、block内のすべての式がチェックされていないコンテキストで評価されます。

checkedステートメントと unchecked ステートメントは、式ではなくブロックで動作することを除き、checkedおよびunchecked演算子 (§12.8.20) と正確に等しくなります。

13.13 lock ステートメント

lock ステートメントは、特定のオブジェクトの相互排他ロックを取得し、ステートメントを実行して、ロックを解放します。

lock_statement
    : 'lock' '(' expression ')' embedded_statement
    ;

lock ステートメントのは、参照であることが知られている型の値を示す必要があります。 暗黙的なボックス化変換 (§10.2.9) は、lock ステートメントのに対して実行されることがないため、式がvalue_typeの値を示すコンパイル時エラーになります。

フォームの lock ステートメント

lock (x)

ここで、 xreference_typeの式であり、次の式と正確に等しくなります。

bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally
{
    if (__lockWasTaken)
    {
        System.Threading.Monitor.Exit(x);
    }
}

ただし、x が評価されるのは 1 回だけです。

相互排他ロックが保持されている間、同じ実行スレッドで実行されるコードでもロックを取得して解放できます。 ただし、他のスレッドで実行されているコードは、ロックが解放されるまでロックの取得をブロックされます。

13.14 using ステートメント

using ステートメントは、1 つ以上のリソースを取得し、ステートメントを実行してから、リソースを破棄します。

using_statement
    : 'using' '(' resource_acquisition ')' embedded_statement
    ;

resource_acquisition
    : local_variable_declaration
    | expression
    ;

リソースは、System.IDisposable インターフェイスを実装するクラスまたは構造体であり、Disposeという名前のパラメーターなしのメソッドが 1 つ含まれています。 リソースを使用しているコードは、 Dispose を呼び出して、リソースが不要であることを示すことができます。

resource_acquisitionの形式がlocal_variable_declaration場合、local_variable_declarationの型は、dynamicまたは暗黙的にSystem.IDisposableに変換できる型のいずれかになります。 resource_acquisitionの形式が expression の場合この式は暗黙的にSystem.IDisposableに変換できます。

resource_acquisitionで宣言されたローカル変数は読み取り専用であり、初期化子を含める必要があります。 埋め込みステートメントが (代入演算子または ++ 演算子と -- 演算子を使用して) これらのローカル変数を変更しようとした場合、またはそれらのアドレスを参照パラメーターまたは出力パラメーターとして渡そうとすると、コンパイル時エラーが発生します。

usingステートメントは、取得、使用、破棄の 3 つの部分に変換されます。 リソースの使用は、finally句を含むtry ステートメントで暗黙的に囲まれます。 この finally 句は、リソースを破棄します。 null リソースが取得された場合、Disposeの呼び出しは行われず、例外はスローされません。 リソースの種類がdynamic場合は、使用と破棄の前に変換が成功するように、取得中にIDisposableへの暗黙的な動的変換 (§10.2.10) によって動的に変換されます。

フォームの using ステートメント

using (ResourceType resource = «expression» ) «statement»

は、3 つの可能な拡張のいずれかに対応します。 ResourceTypeが null 非許容値型または値型制約 (§15.2.5 の型パラメーターである場合、拡張は意味的に

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        ((IDisposable)resource).Dispose();
    }
}

ただし、 resourceSystem.IDisposable にキャストしても、ボックス化は発生しません。

それ以外の場合、 ResourceTypedynamicされると、展開は

{
    ResourceType resource = «expression»;
    IDisposable d = resource;
    try
    {
        «statement»;
    }
    finally
    {
        if (d != null)
        {
            d.Dispose();
        }
    }
}

それ以外の場合、展開は次の値になります。

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        IDisposable d = (IDisposable)resource;
        if (d != null)
        {
            d.Dispose();
        }
    }
}

どの展開でも、 resource 変数は埋め込みステートメントでは読み取り専用であり、 d 変数は埋め込みステートメントでアクセスできなくなり、非表示になります。

特定の using_statement の実装は、パフォーマンス上の理由から、上記の拡張と一貫性がある限り、異なる方法で実装することが許可されます。

形式の using ステートメント:

using («expression») «statement»

は、同じ 3 つの可能な拡張を持っています。 この場合、 ResourceType は暗黙的に expression のコンパイル時の型になります (ある場合)。 それ以外の場合は、インターフェイス IDisposable 自体が ResourceTypeとして使用されます。 resource変数は、埋め込まれた statement ではアクセスできません。また、表示されません。

resource_acquisitionlocal_variable_declarationの形式になると、特定の型の複数のリソースを取得できます。 フォームの using ステートメント

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»

は、入れ子になった using ステートメントのシーケンスと正確に等しくなります。

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»

: 次の例では、log.txtという名前のファイルを作成し、2 行のテキストをファイルに書き込みます。 次に、読み取り用に同じファイルを開き、含まれているテキスト行をコンソールにコピーします。

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);
            }
        }
    }
}

TextWriterクラスとTextReader クラスはIDisposable インターフェイスを実装するため、この例では、using ステートメントを使用して、書き込み操作または読み取り操作の後に基になるファイルが適切に閉じられるようにすることができます。

end の例

13.15 yield ステートメント

yieldステートメントは、反復子ブロック (§13.3) で使用され、反復子の列挙子オブジェクト (§15.14.5) または列挙可能なオブジェクト (§15.14.6) に値を返すか、イテレーションの終了を通知します。

yield_statement
    : 'yield' 'return' expression ';'
    | 'yield' 'break' ';'
    ;

yield はコンテキスト キーワード (§6.4.4) であり、 return または break キーワードの直前に使用する場合にのみ特別な意味を持ちます。

次に示すように、 yield ステートメントを使用できる場所にはいくつかの制限があります。

  • method_bodyoperator_body、またはaccessor_bodyの外部にyieldステートメント (いずれかの形式) が表示されるコンパイル時エラーです。
  • これは、(いずれかの形式の) yield ステートメントが匿名関数内に表示されるコンパイル時エラーです。
  • try ステートメントのfinally句にyield ステートメント (いずれかの形式) が含まれる場合は、コンパイル時エラーです。
  • catch_clausesを含むtry ステートメント内の任意の場所にyield return ステートメントが表示される場合は、コンパイル時エラーです。

: 次の例は、 yield ステートメントの有効および無効な使用方法を示しています。

delegate IEnumerable<int> D();

IEnumerator<int> GetEnumerator()
{
    try
    {
        yield return 1; // Ok
        yield break;    // Ok
    }
    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;    // Ok
    }
    catch
    {
        yield return 4; // Error, yield return in try/catch
        yield break;    // Ok
    }
    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
}

end の例

暗黙的な変換 (§10.2) は、 yield return ステートメントの式の型から反復子の yield 型 (§15.14.4) に存在する必要があります。

yield return ステートメントは次のように実行されます。

  • ステートメントで指定された式が評価され、暗黙的に yield 型に変換され、列挙子オブジェクトの Current プロパティに割り当てられます。
  • 反復子ブロックの実行が中断されます。 yield return ステートメントが 1 つ以上のtry ブロック内にある場合、関連付けられているfinally ブロックは現時点で実行されません。
  • 列挙子オブジェクトの MoveNext メソッドは、 true を呼び出し元に返し、列挙子オブジェクトが次の項目に正常に進んだことを示します。

列挙子オブジェクトの MoveNext メソッドの次の呼び出しは、最後に中断された場所から反復子ブロックの実行を再開します。

yield break ステートメントは次のように実行されます。

  • yield break ステートメントが、関連付けられたfinally ブロックを持つ 1 つ以上のtry ブロックで囲まれている場合、制御は最初に最も内側のtry ステートメントのfinally ブロックに転送されます。 コントロールが finally ブロックの終点に達すると、次に囲むステートメントの finally ブロックに制御 try 転送されます。 このプロセスは、外側のすべての try ステートメントのfinally ブロックが実行されるまで繰り返されます。
  • 反復子ブロックの呼び出し元に制御が返されます。 これは、列挙子オブジェクトの MoveNext メソッドまたは Dispose メソッドです。

yield break ステートメントは無条件に制御を他の場所に転送するため、yield break ステートメントのエンドポイントに到達できません。