次の方法で共有


9 変数

9.1 全般

変数は、ストレージの場所を表します。 すべての変数には、変数に格納できる値を決定する型があります。 C# は型セーフな言語であり、C# コンパイラでは、変数に格納されている値が常に適切な型であることが保証されます。 変数の値は、代入または ++ 演算子と -- 演算子を使用して変更できます。

変数の値を取得する前に 無期限に割り当て (§9.4) する必要があります。

次のサブクラスで説明されているように、変数は初期化的に割り当てられるか割り当て解除。 最初に割り当てられた変数には適切に定義された初期値があり、常に確実に割り当てられていると見なされます。 最初に割り当てられていない変数には初期値がありません。 最初に割り当てられていない変数が特定の場所で確実に割り当てられていると見なされるためには、その場所に至る実行パスごとに変数への割り当てが行われます。

9.2 変数カテゴリ

9.2.1 全般

C# では、静的変数、インスタンス変数、配列要素、値パラメーター、入力パラメーター、参照パラメーター、出力パラメーター、ローカル変数の 8 つのカテゴリが定義されています。 以下のサブクラウスでは、これらの各カテゴリについて説明します。

: 次のコード内

class A
{
    public static int x;
    int y;

    void F(int[] v, int a, ref int b, out int c, in int d)
    {
        int i = 1;
        c = a + b++ + d;
    }
}

x は静的変数、 y はインスタンス変数、 v[0] は配列要素、 a は値パラメーター、 b は参照パラメーター、 c は出力パラメーター、 d は入力パラメーター、 i はローカル変数です。 end の例

9.2.2 静的変数

static修飾子で宣言されたフィールドは静的変数です。 静的変数は、その包含型の static コンストラクター (§15.12) を実行する前に存在し、関連付けられているアプリケーション ドメインが存在しなくなったときに存在しなくなります。

静的変数の初期値は、変数の型の既定値 (§9.3) です。

明確な代入チェックを行う目的で、静的変数は最初に割り当てられたものと見なされます。

9.2.3 インスタンス変数

9.2.3.1 全般

static修飾子なしで宣言されたフィールドは、インスタンス変数です。

9.2.3.2 クラス内のインスタンス変数

クラスのインスタンス変数は、そのクラスの新しいインスタンスが作成されるときに存在し、そのインスタンスへの参照がなく、インスタンスのファイナライザー (存在する場合) が実行されると存在しなくなります。

クラスのインスタンス変数の初期値は、変数の型の既定値 (§9.3) です。

明確な代入チェックを目的として、クラスのインスタンス変数は最初に割り当てられていると見なされます。

9.2.3.3 構造体内のインスタンス変数

構造体のインスタンス変数の有効期間は、構造体が属する構造体変数とまったく同じです。 言い換えると、構造体型の変数が存在する場合や存在しなくなった場合は、構造体のインスタンス変数も存在します。

構造体のインスタンス変数の初期代入状態は、含まれる struct 変数と同じです。 言い換えると、構造体変数が最初に割り当てられたと見なされる場合、インスタンス変数も同様であり、構造体変数が最初に割り当てられていないと見なされると、そのインスタンス変数も同様に割り当て解除されます。

9.2.4 配列要素

配列インスタンスの作成時に配列の要素が存在し、その配列インスタンスへの参照がない場合は存在しなくなります。

配列の各要素の初期値は、配列要素の型の既定値 (§9.3) です。

明確な代入チェックを目的として、配列要素は最初に割り当てられたと見なされます。

9.2.5 値パラメーター

値パラメーターは、パラメーターが属する関数メンバー (メソッド、インスタンス コンストラクター、アクセサー、または演算子) または匿名関数の呼び出し時に存在し、呼び出しで指定された引数の値で初期化されます。 通常、関数本体の実行が完了すると、値パラメーターは存在しなくなります。 ただし、値パラメーターが匿名関数 (§12.19.6.2) によってキャプチャされる場合、その有効期間は、少なくともその匿名関数から作成されたデリゲートまたは式ツリーがガベージ コレクションの対象になるまで拡張されます。

明確な代入チェックを目的として、値パラメーターは最初に割り当てられていると見なされます。

値パラメーターについては、 §15.6.2.2 で詳しく説明します。

9.2.6 参照パラメーター

参照パラメーターは、関数メンバー、デリゲート、匿名関数、またはローカル関数の呼び出し時に存在する参照変数 (§9.7) であり、その参照先はその呼び出しの引数として指定された変数に初期化されます。 関数本体の実行が完了すると、参照パラメーターは存在しなくなります。 値パラメーターとは異なり、参照パラメーターはキャプチャされません (§9.7.2.9)。

参照パラメーターには、次の明確な割り当て規則が適用されます。

: 出力パラメーターの規則は異なり、(§9.2.7) で説明されています。 end note

  • 変数は、関数メンバーまたはデリゲート呼び出しで参照パラメーターとして渡される前に、確実に代入されます (§9.4)。
  • 関数メンバーまたは匿名関数内では、最初に割り当てられた参照パラメーターと見なされます。

参照パラメーターについては、 §15.6.2.3.3 で詳しく説明します。

9.2.7 出力パラメーター

出力パラメーターは、関数メンバー、デリゲート、匿名関数、またはローカル関数の呼び出し時に存在する参照変数 (§9.7) であり、その参照先はその呼び出しの引数として指定された変数に初期化されます。 関数本体の実行が完了すると、出力パラメーターは存在しなくなります。 値パラメーターとは異なり、出力パラメーターはキャプチャされません (§9.7.2.9)。

出力パラメーターには、次の明確な割り当て規則が適用されます。

: 参照パラメーターの規則は異なり、(§9.2.6 で説明されています。 end note

  • 変数を関数メンバーまたはデリゲート呼び出しで出力パラメーターとして渡す前に、変数を確実に割り当てる必要はありません。
  • 関数メンバーまたはデリゲートの呼び出しが正常に完了した後、出力パラメーターとして渡された各変数は、その実行パスで割り当てられていると見なされます。
  • 関数メンバーまたは匿名関数内では、出力パラメーターは最初は割り当てられていないと見なされます。
  • 関数メンバー、匿名関数、またはローカル関数のすべての出力パラメーターは、関数メンバー、匿名関数、またはローカル関数が正常に戻る前に確実に割り当てられます (§9.4)。

出力パラメーターについては、 §15.6.2.3.4 で詳しく説明します。

9.2.8 入力パラメータ

入力パラメーターは、関数メンバー、デリゲート、匿名関数、またはローカル関数の呼び出し時に存在する参照変数 (§9.7) であり、その参照先はその呼び出しの引数として指定された variable_reference に初期化されます。 関数本体の実行が完了すると、入力パラメーターは存在しなくなります。 値パラメーターとは異なり、入力パラメーターはキャプチャされません (§9.7.2.9)。

次の明確な割り当て規則は、入力パラメーターに適用されます。

  • 変数は、関数メンバーまたはデリゲート呼び出しで入力パラメーターとして渡される前に、確実に代入されます (§9.4)。
  • 関数メンバー、匿名関数、またはローカル関数内では、入力パラメーターは最初に割り当てられたと見なされます。

入力パラメーターについては、 §15.6.2.3.2 で詳しく説明します。

9.2.9 ローカル変数

ローカル変数は、try_statementlocal_variable_declarationdeclaration_expressionforeach_statement、またはspecific_catch_clauseによって宣言されます。 ローカル変数は、特定の種類の pattern (§11) で宣言することもできます。 foreach_statementの場合、ローカル変数は反復変数です (§13.9.5)。 specific_catch_clauseの場合、ローカル変数は例外変数 (§13.11) です。 foreach_statementまたはspecific_catch_clauseによって宣言されたローカル変数は、最初に割り当てられたものと見なされます。

local_variable_declarationは、ブロック、for_statementswitch_block、またはusing_statementで発生する可能性があります。 declaration_expressionは、out argument_valueとして、および分解割り当てのターゲットであるtuple_elementとして発生する可能性があります (§12.21.2)。

ローカル変数の有効期間は、プログラムの実行の一部であり、その間にストレージが予約されていることが保証されます。 この有効期間は、少なくともそのスコープの実行が何らかの方法で終了するまで、関連付けられているスコープへのエントリから拡張されます。 (囲まれた ブロックの入力メソッドの呼び出し、反復子ブロックからの値の生成は、現在のスコープの実行を中断しますが、終了しません)。ローカル変数が匿名関数 (§12.19.6.2) によってキャプチャされる場合、その有効期間は、少なくとも匿名関数から作成されたデリゲートまたは式ツリーと、キャプチャされた変数を参照する他のオブジェクトがガベージ コレクションの対象になるまで拡張されます。 親スコープが再帰的または反復的に入力される場合、ローカル変数の新しいインスタンスが毎回作成され、その初期化子 (存在する場合) が毎回評価されます。

: ローカル変数は、スコープが入力されるたびにインスタンス化されます。 この動作は、匿名メソッドを含むユーザー コードに表示されます。 end note

: foreach_statementによって宣言された iteration 変数 (§13.9.5) の有効期間は、そのステートメントの 1 回の反復です。 イテレーションごとに新しい変数が作成されます。 end note

: ローカル変数の実際の有効期間は実装に依存します。 たとえば、コンパイラは、ブロック内のローカル変数がそのブロックのごく一部にのみ使用されることを静的に判断する場合があります。 この分析を使用すると、コンパイラによってコードが生成され、その結果、変数のストレージの有効期間が、格納ブロックよりも短くなります。

ローカル参照変数によって参照されるストレージは、そのローカル参照変数の有効期間とは別に再利用されます (§7.9)。

end note

local_variable_declarationまたはdeclaration_expressionによって導入されたローカル変数は自動的に初期化されないため、既定値はありません。 このようなローカル変数は、最初は割り当てられていないと見なされます。

: 初期化子を含む local_variable_declaration は、最初はまだ割り当てされていません。 宣言の実行は、変数への代入とまったく同じように動作します (§9.4.4.5)。 初期化子が実行される前に変数を使用する。たとえば、初期化子式自体内、または初期化子をバイパスする goto_statement を使用する場合などです。コンパイル時エラーです。

goto L;

int x = 1; // never executed

L: x += 1; // error: x not definitely assigned

ローカル変数のスコープ内では、宣言子の前のテキスト位置でそのローカル変数を参照するのはコンパイル時エラーです。

end note

9.2.9.1 破棄

discard は、名前のないローカル変数です。 破棄は、識別子_を持つ宣言式 (§12.17) によって導入され、暗黙的に型指定 (_またはvar _) または明示的に型指定 (T _) されます。

: _ は、多くの形式の宣言で有効な識別子です。 end note

破棄には名前がないため、それが表す変数への唯一の参照は、それを導入する式です。

: ただし、破棄は出力引数として渡すことができます。これにより、対応する出力パラメーターは、関連付けられているストレージの場所を示すことができます。 end note

破棄は最初は割り当てられないため、その値にアクセスするのは常にエラーです。

例:

_ = "Hello".Length;
(int, int, int) M(out int i1, out int i2, out int i3) { ... }
(int _, var _, _) = M(out int _, out var _, out _);

この例では、スコープ内に名前 _ 宣言がないことを前提としています。

_への代入は、式の結果を無視するための単純なパターンを示しています。 Mの呼び出しは、タプルと出力パラメーターで使用できる破棄のさまざまな形式を示しています。

end の例

9.3 既定値

次のカテゴリの変数は、既定値に自動的に初期化されます。

  • 静的変数
  • クラス インスタンスのインスタンス変数。
  • 配列の要素。

変数の既定値は変数の型によって異なり、次のように決定されます。

  • value_typeの変数の場合、既定値は、value_typeの既定のコンストラクター (§8.3.3) によって計算された値と同じです。
  • reference_typeの変数の場合、既定値は null です。

: 既定値への初期化は、通常、メモリ マネージャーまたはガベージ コレクターが使用のために割り当てられる前に、すべてのビットゼロにメモリを初期化することによって行われます。 このため、すべてビット 0 を使用して null 参照を表すのが便利です。 end note

9.4 明確な割り当て

9.4.1 全般

関数メンバーまたは匿名関数の実行可能コード内の特定の場所で、変数は 無期限に割り当てられると言われます 特定の静的フロー分析 (§9.4.4) によって、変数が自動的に初期化されたか、少なくとも 1 つの代入のターゲットであることがコンパイラによって証明できる場合です。

: 非公式に述べたように、明確な割り当てのルールは次のとおりです。

  • 最初に割り当てられた変数 (§9.4.2) は、常に確実に割り当てられていると見なされます。
  • 最初に割り当てられていない変数 (§9.4.3) は、その場所に至る可能性のあるすべての実行パスに次の少なくとも 1 つが含まれている場合、特定の場所で確実に割り当てられていると見なされます。
    • 変数が左オペランドである単純な代入 (§12.21.2)。
    • 出力パラメーターとして変数を渡す呼び出し式 (§12.8.9) またはオブジェクト作成式 (§12.8.16.2)。
    • ローカル変数の場合、変数初期化子を含む変数 (§13.6.2) のローカル変数宣言。

上記の非公式ルールの基になる正式な仕様については、 §9.4.2§9.4.3、および §9.4.4 で説明されています。

end note

struct_type変数のインスタンス変数の明確な代入状態は、個別に追跡されるだけでなく、まとめて追跡されます。 §9.4.2§9.4.3、および §9.4.4 で説明されている規則の他に、struct_type変数とそのインスタンス変数には次の規則が適用されます。

  • インスタンス変数を含む struct_type 変数が確実に割り当てられていると見なされる場合、インスタンス変数は確実に割り当てられていると見なされます。
  • 各インスタンス変数が確実に割り当てられていると見なされる場合、 struct_type 変数は確実に割り当てられていると見なされます。

明確な割り当ては、次のコンテキストでの要件です。

  • 変数は、値が取得される各場所に確実に割り当てられます。

    : これにより、未定義の値が発生することはありません。 end note

    式内の変数の出現は、次の場合を除き、変数の値を取得すると見なされます。

    • 変数は単純代入の左オペランドです。
    • 変数が出力パラメーターとして渡されるか、または
    • 変数は struct_type 変数であり、メンバー アクセスの左オペランドとして発生します。
  • 変数は、参照パラメーターとして渡される各場所に確実に割り当てられます。

    : これにより、呼び出される関数メンバーが最初に割り当てられた参照パラメーターを考慮できるようになります。 end note

  • 変数は、入力パラメーターとして渡される各場所に確実に割り当てられます。

    : これにより、呼び出される関数メンバーが最初に割り当てられた入力パラメーターを考慮できるようになります。 end note

  • 関数メンバーのすべての出力パラメーターは、関数メンバーが返す各場所 (return ステートメントまたは関数メンバー本体の末尾に到達する実行を通じて) に確実に割り当てられます。

    : これにより、関数メンバーは出力パラメーターで未定義の値を返さないため、コンパイラは変数を変数への代入と同等の出力パラメーターとして受け取る関数メンバー呼び出しを考慮できます。 end note

  • struct_typeインスタンス コンストラクターのthis変数は、そのインスタンス コンストラクターが返す各場所に確実に割り当てられます。

9.4.2 最初に割り当てられた変数

変数の次のカテゴリは、最初に割り当てられたものとして分類されます。

  • 静的変数
  • クラス インスタンスのインスタンス変数。
  • 最初に割り当てられた構造体変数のインスタンス変数。
  • 配列の要素。
  • 値パラメーター。
  • 参照パラメーター。
  • 入力パラメーター。
  • catch句または foreach ステートメントで宣言された変数。

9.4.3 最初に割り当てられていない変数

変数の次のカテゴリは、最初は未割り当てとして分類されます。

  • 最初に割り当てられていない構造体変数のインスタンス変数。
  • コンストラクター初期化子のない構造体インスタンス コンストラクターの this 変数を含む出力パラメーター。
  • catch句または foreach ステートメントで宣言されているものを除くローカル変数。

9.4.4 明確な割り当てを決定するための正確なルール

9.4.4.1 全般

使用される各変数が確実に割り当てられていることを確認するために、コンパイラは、このサブドキュメントで説明されているプロセスと同等のプロセスを使用する必要があります。

コンパイラは、最初に 1 つ以上の未割り当て変数を持つ各関数メンバーの本体を処理します。 最初に割り当てられていない変数 v ごとに、コンパイラは関数メンバー内の次の各ポイントで、vdefinite-assignment 状態を決定します。

  • 各ステートメントの先頭
  • 各ステートメントのエンドポイント (§13.2)
  • コントロールを別のステートメントまたはステートメントの終点に転送する各アーク
  • 各式の先頭
  • 各式の末尾

vの確定代入状態は、次のいずれかになります。

  • 確実に割り当てられます。 これは、この時点までのすべての可能な制御フローで、 v に値が割り当てられていることを示します。
  • 間違いなく割り当てません。 bool型の式の最後にある変数の状態の場合、確実に割り当てられない変数の状態は、次のいずれかのサブ状態に分類される可能性があります (ただし、必ずしもそうであるとは限りません)。
    • true 式の後に確実に割り当てられます。 この状態は、ブール式が true として評価された場合、 v が確実に割り当てられていることを示しますが、ブール式が false と評価された場合は必ずしも割り当てされません。
    • false 式の後に確実に割り当てられます。 この状態は、ブール式が false と評価された場合、 v が確実に割り当てられていることを示しますが、ブール式が true と評価された場合は必ずしも割り当てされません。

次の規則は、変数 v の状態が各場所でどのように決定されるかを制御します。

9.4.4.2 ステートメントの一般的な規則

  • v は、関数メンバー本体の先頭に確実に割り当てられない。
  • 他のステートメントの先頭にある v の確定代入状態は、そのステートメントの先頭を対象とするすべての制御フロー転送で、 v の確定代入状態を確認することによって決定されます。 このようなすべての制御フロー転送で v が確実に割り当てられている場合は、ステートメントの先頭に v が確実に割り当てられます。 制御フロー転送の可能性のセットは、ステートメントの到達可能性を確認する場合と同じ方法で決定されます (§13.2)。
  • blockcheckeduncheckedifwhiledoforforeachlockusing、またはswitchステートメントのエンドポイントでのvの明確な割り当て状態は、そのステートメントのエンドポイントを対象とするすべての制御フロー転送でvの確定代入状態を確認することによって決定されます。 vがこのようなすべての制御フロー転送に確実に割り当てられている場合、vはステートメントの最後に確実に割り当てられます。 それ以外の場合、 v はステートメントの最後に確実に割り当てません。 制御フロー転送の可能性のセットは、ステートメントの到達可能性を確認する場合と同じ方法で決定されます (§13.2)。

: 到達不能ステートメントへの制御パスがないため、 v は到達不能ステートメントの先頭に確実に割り当てられます。 end note

9.4.4.3 ステートメント、チェック済み、およびチェックされていないステートメントをブロックする

コントロールのvブロック内のステートメント リストの最初のステートメント (またはステートメント リストが空の場合はブロックの終了位置) への制御転送の確定代入状態は、ブロック、checked、またはuncheckedステートメントの前の v の確定代入ステートメントと同じです。

9.4.4.4 式ステートメント

expr で構成される式ステートメント stmtの場合:

  • v は、expr の開始時と同じ明確な割り当て状態stmtの開始時と同じです。
  • vexprの終了時に確実に割り当てられている場合は、stmtの終点に確実に割り当てられます。それ以外の場合は、stmtのエンドポイントで確実に割り当てません。

9.4.4.5 宣言ステートメント

  • stmtが初期化子のない宣言ステートメントである場合、vstmtのエンドポイントでstmtの開始時と同じ確定代入状態になります。
  • stmtが初期化子を持つ宣言ステートメントである場合、vの確定代入状態は、stmtがステートメント リストであるかのように決定され、初期化子を持つ宣言ごとに 1 つの代入ステートメント (宣言順)。

9.4.4.6 If ステートメント

形式のステートメント stmt の場合:

if ( «expr» ) «then_stmt» else «else_stmt»
  • v は、expr の開始時と同じ明確な割り当て状態stmtの開始時と同じです。
  • vexprの終了時に確実に割り当てられる場合、then_stmtへの制御フロー転送と、else_stmtまたは stmt のエンドポイントへの制御フロー転送で確実に割り当てられます else 句がない場合。
  • vexprの最後に "true 式の後に確実に割り当てられる" 状態である場合、then_stmtへの制御フロー転送では確実に割り当てられ、他の句がない場合はelse_stmtまたは stmt のエンドポイントへの制御フロー転送には確実に割り当てません。
  • vexprの最後に "false 式の後に確実に割り当てられる" 状態である場合、else_stmtへの制御フロー転送に確実に割り当てられ、then_stmtへの制御フロー転送では確実に割り当てられない。 then_stmtのエンドポイントで確実に割り当てられている場合にのみstmt のエンドポイントで確実に割り当てられます。
  • それ以外の場合、v は、then_stmtまたはelse_stmtへの制御フロー転送、または else 句がない場合はstmt のエンドポイントへの制御フロー転送では確実に割り当てられないと見なされます。

9.4.4.7 Switch ステートメント

制御式がexprswitchステートメントstmtの場合:

exprの開始時のvの確定代入状態はstmtの先頭にあるvの状態と同じです。

ケースのガード句の先頭にある v の明確な代入状態は次のようになります。

  • vswitch_labelで宣言されたパターン変数である場合: "確実に割り当てられます"。
  • そのガード句 (§13.8.3) を含むスイッチ ラベルに到達できない場合: "確実に割り当て済み" です。
  • それ以外の場合、v の状態は、expr 後の v の状態と同じです。

: 2 番目の規則では、アクセスできないコードで割り当てられていない変数にアクセスした場合に、コンパイラがエラーを発行する必要がなくなります。 bの状態は、到達不能なスイッチ ラベル case 2 when bに "確実に割り当てられます" です。

bool b;
switch (1) 
{
    case 2 when b: // b is definitely assigned here.
    break;
}

end の例

到達可能な switch ブロック ステートメント リストへの制御フロー転送の v の明確な割り当て状態は、

  • 制御転送の原因が 'goto case' または 'goto default' ステートメントの場合、 v の状態は、その 'goto' ステートメントの先頭の状態と同じです。
  • 制御転送がスイッチのdefault ラベルによるものである場合、v の状態は、expr 後の v の状態と同じです。
  • 制御転送が到達不能スイッチ ラベルによるものである場合、 v の状態は "確実に割り当てられます" です。
  • 制御転送がガード句を持つ到達可能なスイッチ ラベルによるものである場合、 v の状態は、guard 句の後の v の状態と同じです。
  • 制御転送がガード句のない到達可能なスイッチ ラベルによるものである場合、 v の状態は次のようになります。
    • vswitch_labelで宣言されたパターン変数である場合: "確実に割り当てられます"。
    • それ以外の場合、v の状態は、expr 後の v の統計と同じです。

これらの規則の結果として、 switch_label で宣言されたパターン変数は、そのセクションの唯一の到達可能なスイッチ ラベルでない場合、switch セクションのステートメントで "確実に割り当てられない" ことになります。

例:

public static double ComputeArea(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            // none of s, c, t, or r is definitely assigned
            return 0;
        case Square s:
            // s is definitely assigned
            return s.Side * s.Side;
        case Circle c:
            // c is definitely assigned
            return c.Radius * c.Radius * Math.PI;
           …
    }
}

end の例

9.4.4.8 While ステートメント

形式のステートメント stmt の場合:

while ( «expr» ) «while_body»
  • v は、expr の開始時と同じ明確な割り当て状態stmtの開始時と同じです。
  • vexprの最後に確実に割り当てられている場合は、while_bodystmtのエンドポイントへの制御フロー転送に確実に割り当てられます。
  • vexprの最後に "true 式の後に確実に割り当てられる" 状態である場合、while_bodyへの制御フロー転送では確実に割り当てられますが、stmtのエンドポイントでは確実に割り当てません。
  • vexprの終了時に "false 式の後に確実に割り当てられる" 状態である場合、stmtのエンドポイントへの制御フロー転送では確実に割り当てられますが、while_bodyへの制御フロー転送では確実に割り当てません。

9.4.4.9 Do ステートメント

形式のステートメント stmt の場合:

do «do_body» while ( «expr» ) ;
  • v は、stmt から stmt の開始時と同じdo_bodyへの制御フロー転送の確定割り当て状態を持
  • v は、do_bodyの終点と同じ明確な割り当て状態exprを持っています。
  • vexprの最後に確実に割り当てられている場合は、制御フローの転送時にstmtのエンドポイントに確実に割り当てられます。
  • vの状態がexprの終了時に "false 式の後に確実に割り当てられる" 場合、stmtのエンドポイントへの制御フロー転送では確実に割り当てられますが、do_bodyへの制御フロー転送では確実に割り当てません。

9.4.4.10 For ステートメント

形式のステートメントの場合:

for ( «for_initializer» ; «for_condition» ; «for_iterator» )
    «embedded_statement»

明確な代入チェックは、ステートメントが記述されたかのように行われます。

{
    «for_initializer» ;
    while ( «for_condition» )
    {
        «embedded_statement» ;
        LLoop: «for_iterator» ;
    }
}

には、ラベル LLoopをターゲットとするgoto ステートメントに変換されるfor ステートメントを対象とするcontinueステートメントが含まれます。 forステートメントからfor_conditionを省略すると、上記の拡張でfor_conditionが true に置き換えられたかのように、確定代入の評価が続行されます。

9.4.4.11 ステートメントの中断、続行、およびジャンプ

breakcontinue、またはgotoステートメントによって発生する制御フロー転送のvの明確な代入状態は、ステートメントの先頭にある v の確定代入状態と同じです。

9.4.4.12 Throw ステートメント

形式のステートメント stmt の場合:

throw «expr» ;

exprの開始時のvの確定代入状態は、stmtの開始時のvの確定代入状態と同じです。

9.4.4.13 Return ステートメント

形式のステートメント stmt の場合:

return «expr» ;
  • exprの開始時のvの確定代入状態は、stmtの開始時のvの確定代入状態と同じです。
  • vが出力パラメーターである場合は、次のいずれかを確実に割り当てる必要があります。
    • expr の後
    • または、return ステートメントを囲むtry-finallyまたはtry-catch-finallyfinally ブロックの末尾。

形式のステートメント stmt の場合:

return ;
  • vが出力パラメーターである場合は、次のいずれかを確実に割り当てる必要があります。
    • stmt の前
    • または、return ステートメントを囲むtry-finallyまたはtry-catch-finallyfinally ブロックの末尾。

9.4.4.14 Try-catch ステートメント

形式のステートメント stmt の場合:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • try_blockの開始時の v の確定代入状態は、v stmt の開始時の確定代入状態と同じです
  • catch_block_iの開始時のvの確定代入状態 (任意のi) は、stmtの開始時のvの確定代入状態と同じです。
  • stmtのエンドポイントでのvの明確な代入状態はvtry_blockのエンドポイントとすべてのcatch_block_iで確実に割り当てられている場合に確実に割り当てられます(すべてのi1から)。

9.4.4.15 Try-finally ステートメント

形式のステートメント stmt の場合:

try «try_block» finally «finally_block»
  • try_blockの開始時の v の確定代入状態は、v stmt の開始時の確定代入状態と同じです
  • finally_blockの開始時のvの確定代入状態は、stmtの開始時のvの確定代入状態と同じです。
  • stmtのエンドポイントでのvの明確な代入状態は、次の少なくとも1つが当てはまる場合にのみ確実に割り当てられます。
    • v は、 try_blockのエンドポイントで確実に割り当てられます
    • v は、 finally_blockのエンドポイントで確実に割り当てられます

try_block内で開始し、try_block外で終了する制御フロー転送 (goto ステートメントなど) が行われた場合、finally_blockのエンドポイントでvが確実に割り当てられている場合、vはその制御フロー転送にも確実に割り当てられていると見なされます。 (これは、 v がこの制御フロー転送の別の理由で確実に割り当てられている場合だけでなく、確実に割り当てられていると見なされます)。

9.4.4.16 Try-catch-finally ステートメント

形式のステートメントの場合:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»

明確な代入分析は、ステートメントがtry-catchステートメントを囲むtry-finallyステートメントであるかのように行われます。

try
{
    try «try_block»
    catch ( ... ) «catch_block_1»
    ...
    catch ( ... ) «catch_block_n»
}
finally «finally_block»

: 次の例は、 try ステートメントのさまざまなブロック (§13.11) が明確な割り当てにどのように影響するかを示しています。

class A
{
    static void F()
    {
        int i, j;
        try
        {
            goto LABEL;
            // neither i nor j definitely assigned
            i = 1;
            // i definitely assigned
        }
        catch
        {
            // neither i nor j definitely assigned
            i = 3;
            // i definitely assigned
        }
        finally
        {
            // neither i nor j definitely assigned
            j = 5;
            // j definitely assigned
        }
        // i and j definitely assigned
        LABEL: ;
        // j definitely assigned
    }
}

end の例

9.4.4.17 Foreach ステートメント

形式のステートメント stmt の場合:

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • exprの開始時のvの確定代入状態はstmtの先頭にあるvの状態と同じです。
  • embedded_statementまたは stmt の終点への制御フロー転送のvの明確な割り当て状態はexprの終了時のvの状態と同じです。

9.4.4.18 Using ステートメント

形式のステートメント stmt の場合:

using ( «resource_acquisition» ) «embedded_statement»
  • resource_acquisitionの先頭にある v の明確な代入状態は、stmt の先頭にある v の状態と同じです。
  • embedded_statementへの制御フロー転送のvの明確な割り当て状態は、resource_acquisitionの終了時のvの状態と同じです。

9.4.4.19 Lock ステートメント

形式のステートメント stmt の場合:

lock ( «expr» ) «embedded_statement»
  • exprの開始時のvの確定代入状態はstmtの先頭にあるvの状態と同じです。
  • embedded_statementへの制御フロー転送のvの明確な割り当て状態は、exprの終了時のvの状態と同じです。

9.4.4.20 Yield ステートメント

形式のステートメント stmt の場合:

yield return «expr» ;
  • exprの開始時のvの確定代入状態はstmtの先頭にあるvの状態と同じです。
  • stmtの終了時のvの明確な割り当て状態はexpr の終了時のvの状態と同じです。

yield breakステートメントは、明確な代入状態には影響しません。

9.4.4.21 定数式の一般的な規則

次は、任意の定数式に適用され、適用される可能性がある次のセクションの規則よりも優先されます。

値が true定数式の場合:

  • vが式の前に確実に割り当てられている場合、vは式の後に確実に割り当てられます。
  • それ以外の場合 v は、式の後に "false 式の後に確実に割り当てられます"。

例:

int x;
if (true) {}
else
{
    Console.WriteLine(x);
}

end の例

値が false定数式の場合:

  • vが式の前に確実に割り当てられている場合、vは式の後に確実に割り当てられます。
  • それ以外の場合 v は、式の後に "true 式の後に確実に割り当てられます"。

例:

int x;
if (false)
{
    Console.WriteLine(x);
}

end の例

他のすべての定数式の場合、式の後の v の確定代入状態は、式の前の v の確定代入状態と同じです。

9.4.4.22 単純式の一般的な規則

リテラル (§12.8.2)、単純名 (§12.8.4)、メンバー アクセス式 (§12.8.7)、インデックスのない基本アクセス式 (§12.8.14 typeof 式 (§12.8.17)、既定値の式 (§12.8.20)、 nameof 式 (§12.8.22)、宣言式 (§12.17)。

  • このような式の最後にある v の確定代入状態は、式の先頭にある v の確定代入状態と同じです。

9.4.4.23 埋め込み式を含む式の一般的な規則

The following rules apply to these kinds of expressions: parenthesized expressions (§12.8.5), tuple expressions (§12.8.6), element access expressions (§12.8.11), base access expressions with indexing (§12.8.14), increment and decrement expressions (§12.8.15, §12.9.6), cast expressions (§12.9.7), unary +, -, ~, * expressions, binary +, -, *, /, %, <<, >>, <, <=, >>===!=isas&|^ 式 (§12.10§12.11§12.12§12.13)、複合代入式 (§ 12.21.4)、 checked 式と unchecked 式 (§12.8.19)、配列およびデリゲート作成式 (§12.8.16) 、および await 式 (§12.9.8)。

これらの各式には、固定順序で無条件に評価される 1 つ以上の部分式があります。

: 二項 % 演算子は、演算子の左側、次に右側を評価します。 インデックス作成操作は、インデックス付き式を評価し、左から右の順に各インデックス式を評価します。 end の例

expr の場合、次の順序で評価 expr₁expr₂、...、 exprₓ の部分式があります。

  • expr₁の開始時のvの確定代入状態は、exprの開始時の確定代入状態と同じです。
  • expri (i 1 より大きい) のvの確定代入状態は、expri₋₁ の終了時の確定代入状態と同じです。
  • exprの終了時のvの確定代入状態は、exprₓの終了時の確定代入状態と同じです。

9.4.4.24 呼び出し式とオブジェクト作成式

呼び出すメソッドが部分メソッド宣言を実装していない部分メソッドであるか、呼び出しが省略される条件付きメソッド (§22.5.3.2) である場合、呼び出し後の v の確定代入状態は、呼び出し前の v の確定代入状態と同じです。 それ以外の場合は、次の規則が適用されます。

呼び出し式 expr の形式の場合:

«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )

またはオブジェクト作成式 フォームのexpr :

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • 呼び出し式の場合、primary_expression前のvの明確な代入状態は、expr前のvの状態と同じです。
  • 呼び出し式の場合、arg₁ 前の v の明確な代入状態は、primary_expression後の v の状態と同じです。
  • オブジェクト作成式の場合、arg₁ の前の v の明確な代入状態は、expr 前の v の状態と同じです。
  • 引数 argi ごとに、argi 後の v の明確な代入状態は、inout、またはref修飾子を無視して、通常の式ルールによって決定されます。
  • 1 より大きいiの引数argiごとに、argi 前の v の明確な代入状態は、argi₋₁ 後の v の状態と同じです
  • 変数 v が任意の引数でout引数 (つまり、"out v") として渡された場合、expr 後の v の状態が確実に割り当てられます。 それ以外の場合、expr 後の v の状態は、argₓ 後の v の状態と同じです。
  • 配列初期化子 (§12.8.16.5)、オブジェクト初期化子 (§12.8.16.3)、コレクション初期化子 (§12) の場合 .8.16.4) と匿名オブジェクト初期化子 (§12.8.16.7)、確定代入状態は、これらのコンストラクトが定義される拡張によって決まります。

9.4.4.25 単純代入式

e内の割り当て先のセットを次のように定義します

  • eがタプル式の場合、eの代入ターゲットはeの要素の代入ターゲットの和集合になります。
  • それ以外の場合、e の割り当てターゲットは

形式の式 expr の場合:

«expr_lhs» = «expr_rhs»
  • expr_lhs前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • expr_rhs前のvの確定代入状態は、expr_lhs後のvの確定代入状態と同じです。
  • vexpr_lhsの代入ターゲットである場合、expr後のvの確定代入状態が確実に割り当てられます。 それ以外の場合、構造体型のインスタンス コンストラクター内で割り当てが発生した場合、 および v は、構築されるインスタンスの自動的に実装されるプロパティ P の非表示のバッキング フィールドであり、P を指定するプロパティ アクセスは、expr_lhsの割り当てターゲットであり、expr 後の v の確定割り当て状態が確実に割り当てられます。 それ以外の場合、expr 後の v の確定代入状態は、expr_rhs後の v の確定代入状態と同じです。

: 次のコード内

class A
{
    static void F(int[] arr)
    {
        int x;
        arr[x = 1] = x; // ok
    }
}

arr[x = 1]が 2 番目の単純な代入の左側として評価された後、変数xは確実に割り当てられていると見なされます。

end の例

9.4.4.26 > 式

形式の式 expr の場合:

«expr_first» && «expr_second»
  • expr_first前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • expr_second前のvの明確な代入状態は、expr_first後のvの状態が確実に割り当てられているか、"true 式の後に確実に割り当てられる" 場合にのみ確実に割り当てられます。 それ以外の場合は、確実に割り当てません。
  • expr後のvの明確な代入状態は、次によって決定されます。
    • expr_first後のvの状態が確実に割り当てられている場合は、expr後のvの状態が確実に割り当てられます。
    • それ以外の場合、expr_second後のvの状態が確実に割り当てられ、expr_first後のvの状態が "false 式の後に確実に割り当てられる" 場合は、expr 後の v の状態が確実に割り当てられます。
    • それ以外の場合、expr_second後の v の状態が確実に割り当てられるか、"true 式の後に確実に割り当てられる" 場合、expr 後の v の状態は "true 式の後に確実に割り当てられます" になります。
    • それ以外の場合、expr_first後のvの状態が "false 式の後に確実に割り当てられます"、expr_second後のvの状態が "false 式の後に確実に割り当てられる" 場合、expr後のvの状態は "false 式の後に確実に割り当てられます"。
    • それ以外の場合、expr 後の v の状態は確実には割り当てません。

: 次のコード内

class A
{
    static void F(int x, int y)
    {
        int i;
        if (x >= 0 && (i = y) >= 0)
        {
            // i definitely assigned
        }
        else
        {
            // i not definitely assigned
        }
        // i not definitely assigned
    }
}

変数 i は、 if ステートメントの埋め込みステートメントのいずれかで確実に割り当てられていると見なされますが、他のステートメントには割り当てません。 メソッド Fif ステートメントでは、式(i = y)の実行は常にこの埋め込みステートメントの実行より前にあるため、変数iは最初の埋め込みステートメントで確実に割り当てられます。 これに対し、変数 i は、2 番目の埋め込みステートメントでは確実に割り当てません。これは、 x >= 0 が false をテストし、その結果、変数 i割り当て解除される可能性があるためです。

end の例

9.4.4.27 ||式

形式の式 expr の場合:

«expr_first» || «expr_second»
  • expr_first前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • expr_second前のvの明確な代入状態は、expr_first後のvの状態が確実に割り当てられているか、"true 式の後に確実に割り当てられる" 場合にのみ確実に割り当てられます。 それ以外の場合は、確実に割り当てません。
  • expr後のvの明確な代入ステートメントは、次によって決定されます。
    • expr_first後のvの状態が確実に割り当てられている場合は、expr後のvの状態が確実に割り当てられます。
    • それ以外の場合、expr_second後のvの状態が確実に割り当てられ、expr_first後のvの状態が "true 式の後に確実に割り当てられる" 場合は、expr後のvの状態が確実に割り当てられます。
    • それ以外の場合、expr_second後の v の状態が確実に割り当てられるか、"false 式の後に確実に割り当てられる" 場合、expr 後の v の状態は "false 式の後に確実に割り当てられます" になります。
    • それ以外の場合、expr_first後のvの状態が "true 式の後に確実に割り当てられます"、2 番目のexpr_後の v の状態が "true 式の後に確実に割り当てられる" 場合expr の後の v の状態は "true 式の後に確実に割り当てられます"。
    • それ以外の場合、expr 後の v の状態は確実には割り当てません。

: 次のコード内

class A
{
    static void G(int x, int y)
    {
        int i;
        if (x >= 0 || (i = y) >= 0)
        {
            // i not definitely assigned
        }
        else
        {
            // i definitely assigned
        }
        // i not definitely assigned
    }
}

変数 i は、 if ステートメントの埋め込みステートメントのいずれかで確実に割り当てられていると見なされますが、他のステートメントには割り当てません。 メソッド Gif ステートメントでは、式(i = y)の実行は常にこの埋め込みステートメントの実行よりも優先されるため、2 番目の埋め込みステートメントで変数iが確実に割り当てられます。 これに対し、最初の埋め込みステートメントでは変数 i が確実に割り当てられないのは、 x >= 0 が true をテストし、その結果変数 i割り当て解除される可能性があるためです。

end の例

9.4.4.28 ! 式

形式の式 expr の場合:

! «expr_operand»
  • expr_operand前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • expr後のvの明確な代入状態は、次によって決定されます。
    • expr_operand後のvの状態が確実に割り当てられている場合は、expr後のvの状態が確実に割り当てられます。
    • それ以外の場合、expr_operand後のvの状態が "false 式の後に確実に割り当てられる" 場合、expr 後のvの状態は "true 式の後に確実に割り当てられます"。
    • それ以外の場合、expr_operand後のvの状態が "true 式の後に確実に割り当てられる" 場合、expr 後の v の状態は "false 式の後に確実に割り当てられます" になります。
    • それ以外の場合、expr 後のvの状態は確実に割り当てません。

9.4.4.29 ?? 式

形式の式 expr の場合:

«expr_first» ?? «expr_second»
  • expr_first前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • expr_second前のvの確定代入状態は、expr_first後のvの確定代入状態と同じです。
  • expr後のvの明確な代入ステートメントは、次によって決定されます。
    • expr_firstが値nullを持つ定数式 (§12.23) の場合、expr 後の v の状態は、expr_second後の v の状態と同じです。
    • それ以外の場合、expr 後の v の状態は、expr_first後の v の確定代入状態と同じです。

9.4.4.30 ?: 式

形式の式 expr の場合:

«expr_cond» ? «expr_true» : «expr_false»
  • expr_cond前のvの確定代入状態は、expr前のvの状態と同じです。
  • expr_true前のvの明確な代入状態は、expr_cond後のvの状態が確実に割り当てられるか、"true 式の後に確実に割り当てられる" 場合に確実に割り当てられます。
  • expr_false前のvの明確な代入状態は、expr_cond後のvの状態が確実に割り当てられている場合、または 「false 式の後に確実に割り当てられる」場合に確実に割り当てられます。
  • expr後のvの明確な代入状態は、次によって決定されます。
    • 値がtrueexpr_cond定数式 (§12.23) の場合、expr 後の v の状態は、expr_true後の v の状態と同じです。
    • それ以外の場合、expr_condが値false定数式 (§12.23) の場合、expr後のvの状態はexpr_false後のvの状態と同じです。
    • それ以外の場合、expr_true後のvの状態が確実に割り当てられ、expr_false後のvの状態が確実に割り当てられる場合は、expr後のvの状態が確実に割り当てられます。
    • それ以外の場合、expr 後の v の状態は確実には割り当てません。

9.4.4.31 匿名関数

本文 (block または expression) を持つlambda_expressionまたはanonymous_method_expression exprの場合body:

  • パラメーターの明確な代入状態は、名前付きメソッド (§9.2.6§9.2.7§9.2.8) のパラメーターの場合と同じです。
  • body前の外部変数vの明確な代入状態は、expr前のvの状態と同じです。 つまり、外部変数の明確な代入状態は、匿名関数のコンテキストから継承されます。
  • expr後の外部変数vの明確な代入状態は、expr前のvの状態と同じです。

: 例

class A
{
    delegate bool Filter(int i);
    void F()
    {
        int max;
        // Error, max is not definitely assigned
        Filter f = (int n) => n < max;
        max = 5;
        DoWork(f);
    }
    void DoWork(Filter f) { ... }
}

は、匿名関数が宣言されている場所で max が確実に割り当てられないため、コンパイル時エラーを生成します。

end の例

: 例

class A
{
    delegate void D();
    void F()
    {
        int n;
        D d = () => { n = 1; };
        d();
        // Error, n is not definitely assigned
        Console.WriteLine(n);
    }
}

また、匿名関数内の n への割り当てが匿名関数の外部の n の明確な代入状態に影響しないため、コンパイル時エラーも生成されます。

end の例

9.4.4.32 式をスローする

形式の式 expr の場合:

throwthrown_expr

  • thrown_expr前のvの明確な代入状態は、expr前のvの状態と同じです。
  • expr後のvの明確な割り当て状態は"確実に割り当てられます"。

9.4.4.33 ローカル関数の変数のルール

ローカル関数は、親メソッドのコンテキストで分析されます。 ローカル関数には、関数呼び出しとデリゲート変換という 2 つの制御フロー パスがあります。

各ローカル関数の本体に対する明確な割り当ては、呼び出しサイトごとに個別に定義されます。 呼び出しのたびに、ローカル関数によってキャプチャされた変数は、呼び出し時点で確実に割り当てられた場合、確実に割り当てられていると見なされます。 この時点でローカル関数本体への制御フロー パスも存在し、到達可能と見なされます。 ローカル関数の呼び出し後、関数を離れるすべての制御ポイントで確実に割り当てられたキャプチャされた変数 (return ステートメント、 yield ステートメント、 await 式) は、呼び出し場所の後に確実に割り当てられていると見なされます。

デリゲート変換には、ローカル関数本体への制御フロー パスがあります。 キャプチャされた変数は、変換前に確実に割り当てられている場合、本文に対して確実に割り当てられます。 ローカル関数によって割り当てられた変数は、変換後に割り当てられたものと見なされません。

: 上記は、ローカル関数の呼び出しまたはデリゲート変換のたびに、本体が明確な割り当てのために再分析されることを意味します。 コンパイラは、呼び出しまたはデリゲート変換のたびにローカル関数の本体を再分析する必要はありません。 実装では、その説明と同等の結果が生成される必要があります。 end note

: 次の例は、ローカル関数でキャプチャされた変数に対する明確な割り当てを示しています。 ローカル関数が書き込む前にキャプチャされた変数を読み取る場合は、ローカル関数を呼び出す前に、キャプチャされた変数を確実に割り当てる必要があります。 ローカル関数 F1 割り当てずに s を読み取ります。 sが確実に割り当てられる前にF1が呼び出されると、エラーになります。 F2 は、読む前に i を割り当てます。 iが確実に割り当てられる前に呼び出される場合があります。 さらに、F3F2で確実に割り当てられるため、s2F2後に呼び出される可能性があります。

void M()
{
    string s;
    int i;
    string s2;
   
    // Error: Use of unassigned local variable s:
    F1();
    // OK, F2 assigns i before reading it.
    F2();
    
    // OK, i is definitely assigned in the body of F2:
    s = i.ToString();
    
    // OK. s is now definitely assigned.
    F1();

    // OK, F3 reads s2, which is definitely assigned in F2.
    F3();

    void F1()
    {
        Console.WriteLine(s);
    }
    
    void F2()
    {
        i = 5;
        // OK. i is definitely assigned.
        Console.WriteLine(i);
        s2 = i.ToString();
    }

    void F3()
    {
        Console.WriteLine(s2);
    }
}

end の例

9.4.4.34 is-pattern 式

形式の式 expr の場合:

expr_operandpattern です

  • expr_operand前のvの確定代入状態は、expr前のvの確定代入状態と同じです。
  • 変数 'v' が pattern で宣言されている場合、 expr 後の 'v' の確定代入状態は は "true の場合は確実に割り当てられます"。
  • それ以外の場合、 expr 後の明確な代入状態 'v' は、 expr_operand後の 'v' の確定代入状態と同じです。

9.5 変数参照

variable_referenceは、変数として分類されるです。 variable_referenceは、現在の値のフェッチと新しい値の格納の両方にアクセスできるストレージの場所を表します。

variable_reference
    : expression
    ;

: C および C++ では、 variable_referencelvalue と呼ばれます。 end note

9.6 変数参照の原子性

次のデータ型の読み取りと書き込みはアトミックである必要があります。 boolcharbytesbyteshortushortuintintfloat、および参照型。 さらに、前のリストの基になる型を持つ列挙型の読み取りと書き込みもアトミックである必要があります。 longulongdoubledecimal、ユーザー定義型など、他の型の読み取りと書き込みをアトミックにする必要はありません。 その目的のために設計されたライブラリ関数とは別に、インクリメントやデクリメントの場合など、アトミックな読み取り/変更/書き込みの保証はありません。

9.7 参照変数と戻り値

9.7.1 全般

参照変数は、参照先 (§9.2.6) と呼ばれる別の変数を参照する変数です。 参照変数は、 ref 修飾子で宣言されたローカル変数です。

参照変数は、参照先の値ではなく、参照先への variable_reference (§9.5) を格納します。 参照変数を使用して値が必要な場合、参照先の値が返されます。同様に、参照変数が代入のターゲットである場合、それが代入先の参照先になります。 参照変数が参照する変数、つまり参照先の格納された variable_reference は、ref 代入 (= ref) を使用して変更できます。

例: 次の例は、参照先が配列の要素であるローカル参照変数を示しています。

public class C
{
    public void M()
    {
        int[] arr = new int[10];
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        element += 5; // arr[5] has been incremented by 5
    }     
}

end の例

参照戻り値は、returns by-ref メソッド (§15.6.1) から返されるvariable_referenceです。 この variable_reference は、参照の戻り値の参照先です。

例: 次の例は、参照先が配列フィールドの要素である参照戻り値を示しています。

public class C
{
    private int[] arr = new int[10];

    public ref readonly int M()
    {
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        return ref element; // return reference to arr[5];
    }     
}

end の例

9.7.2 安全なコンテキストの参照

9.7.2.1 全般

すべての参照変数は、参照変数の ref-safe コンテキストが参照先の ref-safe コンテキストより大きくないことを保証する安全規則に従います。

: safe-context の関連概念は、関連する制約と共に (§16.4.12) で定義されています。 end note

変数の場合、その変数の ref-safe-context は、その変数への variable_reference (§9.5) が有効なコンテキストです。 参照変数の参照先には、参照変数自体の ref-safe コンテキストと同じ幅以上の ref-safe コンテキストが含まれている必要があります。

: コンパイラは、プログラム テキストの静的分析によって ref-safe コンテキストを決定します。 ref-safe コンテキストは、実行時の変数の有効期間を反映します。 end note

ref-safe-context には、次の 3 つのコンテキストがあります。

  • declaration-block: ローカル変数 (§9.2.9) へのvariable_referenceの ref-safe コンテキストは、そのスコープ内の入れ子になった embed-statement を含むローカル変数のスコープ (§13.6.2) です。

    ローカル変数への variable_reference は、参照変数がその変数の ref-safe コンテキスト内で宣言されている場合にのみ、参照変数の有効な参照先です。

  • function-member: 関数内では、次のいずれかの variable_reference に関数メンバーの ref-safe コンテキストがあります。

    • クラス メンバー関数の暗黙的なthisを含む、関数メンバー宣言の値パラメーター (§15.6.2.2)。
    • 構造体メンバー関数の暗黙的な参照 (ref) パラメーター (§15.6.2.3.3) this とそのフィールド。

    関数メンバーの ref-safe-context を持つ variable_reference は、参照変数が同じ関数メンバーで宣言されている場合にのみ有効な参照先です。

  • caller-context: 関数内では、次のいずれかに対する variable_reference には、呼び出し元コンテキストの ref-safe コンテキストがあります。

    • 構造体メンバー関数の暗黙的なthis以外の参照パラメーター (§9.2.6;
    • このようなパラメーターのメンバー フィールドと要素。
    • クラス型のパラメーターのメンバー フィールド。そして
    • 配列型のパラメーターの要素。

呼び出し元コンテキストの ref-safe コンテキストを持つ variable_reference は、参照の戻り値の参照先にすることができます。

これらの値は、最も狭い (宣言ブロック) から最も広い (呼び出し元コンテキスト) までの入れ子関係を形成します。 入れ子になった各ブロックは、異なるコンテキストを表します。

: 次のコードは、さまざまな ref-safe コンテキストの例を示しています。 宣言は、参照先が ref 変数の初期化式である ref-safe-context を示しています。 次の例は、参照の戻り値の ref-safe コンテキストを示しています。

public class C
{
    // ref safe context of arr is "caller-context". 
    // ref safe context of arr[i] is "caller-context".
    private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

    // ref safe context is "caller-context"
    public ref int M1(ref int r1)
    {
        return ref r1; // r1 is safe to ref return
    }

    // ref safe context is "function-member"
    public ref int M2(int v1)
    {
        return ref v1; // error: v1 isn't safe to ref return
    }

    public ref int M3()
    {
        int v2 = 5;

        return ref arr[v2]; // arr[v2] is safe to ref return
    }

    public void M4(int p) 
    {
        int v3 = 6;

        // context of r2 is declaration-block,
        // ref safe context of p is function-member
        ref int r2 = ref p;

        // context of r3 is declaration-block,
        // ref safe context of v3 is declaration-block
        ref int r3 = ref v3;

        // context of r4 is declaration-block,
        // ref safe context of arr[v3] is caller-context
        ref int r4 = ref arr[v3]; 
    }
}

end example.

: struct 型の場合、暗黙的な this パラメーターは参照パラメーターとして渡されます。 関数メンバーとして struct 型のフィールドの ref-safe コンテキストでは、これらのフィールドを参照返しで返さないようにします。 この規則により、次のコードが禁止されます。

public struct S
{
     private int n;

     // Disallowed: returning ref of a field.
     public ref int GetN() => ref n;
}

class Test
{
    public ref int M()
    {
        S s = new S();
        ref int numRef = ref s.GetN();
        return ref numRef; // reference to local variable 'numRef' returned
    }
}

end example.

9.7.2.2 ローカル変数 ref safe context

ローカル変数 vの場合:

  • vが参照変数の場合、その ref-safe-context は初期化式の ref-safe-context と同じです。
  • それ以外の場合、その ref-safe-context は declaration-block です。

9.7.2.3 パラメーター ref safe context

パラメーター pの場合:

  • pが参照または入力パラメーターの場合、その ref-safe-context は呼び出し元コンテキストです。 pが入力パラメーターの場合、書き込み可能なrefとして返すことはできませんが、ref readonlyとして返すことができます。
  • pが出力パラメーターの場合、その ref-safe-context は呼び出し元コンテキストです。
  • それ以外の場合、 p が構造体型の this パラメーターである場合、その ref-safe-context は関数メンバーになります。
  • それ以外の場合、パラメーターは値パラメーターであり、その ref-safe-context は関数メンバーです。

9.7.2.4 フィールド参照セーフコンテキスト

フィールドへの参照を指定する変数の場合は、次の e.F

  • eが参照型の場合、その ref-safe-context は呼び出し元コンテキストです。
  • それ以外の場合、 e が値型の場合、その ref-safe-context は eの ref-safe-context と同じです。

9.7.2.5 演算子

条件演算子 (§12.18)、 c ? ref e1 : ref e2、および参照代入演算子 ( = ref e (§12.21.1) には、参照変数がオペランドとして含まれており、参照変数が生成されます。 これらの演算子の場合、結果の ref-safe-context は、すべての ref オペランドの ref-safe-context の中で最も狭いコンテキストです。

9.7.2.6 関数の呼び出し

ref を返す関数の呼び出しの結果 c 変数の場合、その ref-safe-context は次のコンテキストの中で最も狭くなります。

  • 呼び出し元コンテキスト。
  • すべての refout、および in 引数式の ref-safe コンテキスト (受信側を除く)。
  • 各入力パラメーターに対して、変数である対応する式があり、変数の型とパラメーターの型の間に ID 変換が存在する場合は、変数の ref-safe-context、それ以外の場合は最も近い外側のコンテキスト。
  • すべての引数式 (受信側を含む) のセーフ コンテキスト (§16.4.12)。

: 最後の行頭文字は、次のようなコードを処理するために必要です。

ref int M2()
{
    int v = 5;
    // Not valid.
    // ref safe context of "v" is block.
    // Therefore, ref safe context of the return value of M() is block.
    return ref M(ref v);
}

ref int M(ref int p)
{
    return ref p;
}

end の例

プロパティ呼び出しとインデクサー呼び出し ( get または set) は、上記の規則によって基になるアクセサーの関数呼び出しとして扱われます。 ローカル関数の呼び出しは、関数呼び出しです。

9.7.2.7 値

値の ref-safe-context は、最も近い外側のコンテキストです。

: これは、ddynamic型であるM(ref d.Length)などの呼び出しで発生します。 また、入力パラメーターに対応する引数と一致します。 end note

9.7.2.8 コンストラクターの呼び出し

コンストラクターを呼び出す new 式は、構築される型を返すと見なされるメソッド呼び出し (§9.7.2.6) と同じ規則に従います。

9.7.2.9 参照変数に関する制限事項

  • 参照パラメーター、出力パラメーター、入力パラメーター、 ref ローカル、 ref struct 型のパラメーターまたはローカルは、ラムダ式またはローカル関数によってキャプチャされません。
  • 参照パラメーター、出力パラメーター、入力パラメーター、 ref struct 型のパラメーターは、反復子メソッドまたは async メソッドの引数でもありません。
  • ref local でも、ref struct型のローカルも、yield returnステートメントまたはawait式の時点でコンテキスト内に存在することはできません。
  • ref 再割り当てe1 = ref e2の場合、e2の ref-safe コンテキストは、少なくともe1ref-safe-contextと同じ広いコンテキストである必要があります。
  • ref return ステートメント return ref e1の場合、 e1 の ref-safe-context は呼び出し元コンテキストでなければなりません。