次の方法で共有


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 はローカル変数です。 終了サンプル

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) で説明されています。 注釈

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

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

9.2.7 出力パラメーター

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

出力パラメーターには、次の確定代入ルールが適用されます。

: 参照パラメーターのルールは異なります。(§9.2.7) で説明されています。 注釈

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

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

9.2.8 入力パラメーター

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

入力パラメーターには、次の確定代入ルールが適用されます。

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

入力パラメーターについては、§15.6.2.3.4 で詳述します。

9.2.9 ローカル変数

9.2.9.1 全般

ローカル変数は、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は、outargument_value として、および分割代入のターゲットである tuple_element として発生できます (§12.21.2)。

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

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

: foreach_statement が宣言したイテレーション変数 (§13.9.5) の有効期限は、ステートメントの 1 回のイテレーションです。 イテレーションごとに新しい変数が作成されます。 注釈

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

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

注釈

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

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

注釈

9.2.9.2 破棄

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

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

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

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

破棄は初期代入されていないため、その値にアクセスすると常にエラーになります。

:

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

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

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

終了サンプル

9.3 既定値

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

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

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

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

: 既定値への初期化は、通常、使用するために割り当てられる前にメモリ マネージャーまたはガベージ コレクターがメモリを all-bits-zero に初期化することによって行われます。 このため、all-bits-zero を使用して null 参照を表すと便利です。 注釈

9.4 確定代入

9.4.1 全般

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

: 非公式に述べたように、確定代入のルールを以下に示します。

  • 初期代入された変数 (§9.4.2) は、常に確定代入されたものみなされます。
  • その場所に導くすべての可能性のあるプログラム実行パスに少なくとも次が含まれる場合、初期代入解除された変数 (§9.4.3) は、指定の場所に確定代入されたものとみなされます。
    • 変数が左オペランドである単純な代入 (§12.21.2)。
    • 変数を出力パラメーターとして渡す呼び出し式 (§12.8.10) またはオブジェクト作成式 (§12.8.17.2)。
    • ローカル変数の場合、変数初期化子を含む変数のローカル変数宣言 (§13.6.2)。

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

注釈

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

  • 包括 struct_type 変数が確定代入されているとみなされている場合、インスタンス変数は確定代入されたものとみなされます。
  • 各インスタンス変数が確定代入されているとみなされている場合、struct_type 変数は確定代入されたものとみなされます。

確定代入は、次のコンテキストの要件です。

  • 変数は、値が取得される各場所で確定代入されます。

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

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

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

    : これにより、呼び出される関数メンバーが初期代入された参照パラメーターを考慮できるようになります。 注釈

  • 変数は、入力パラメーターとして渡される各場所に確定代入されます。

    : これにより、呼び出される関数メンバーが初期代入された入力パラメーターを考慮できるようになります。 注釈

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

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

  • this インスタンス コントラクターの 変数は、そのインスタンス コンストラクターが戻る各場所で確定代入されます。

9.4.2 初期代入された変数

変数の次のカテゴリは、初期代入変数として分類されます。

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

9.4.3 初期代入解除された変数

変数の次のカテゴリは、初期代入解除変数として分類されます。

  • 初期代入解除された構造体変数のインスタンス変数。
  • コンストラクター初期化子のない構造体インスタンス コンストラクターの this 変数を含む出力パラメーター。
  • catch句または foreach ステートメントで宣言されているローカル変数以外のローカル変数。

9.4.4 確定代入を決定するための正確なルール

9.4.4.1 全般

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

関数メンバーの本体は、初期代入解除された 1 つ以上の変数を宣言できます。 最初に割り当てられていない変数 v ごとに、コンパイラは関数メンバー内の次の各ポイントで v明確な代入状態を決定する必要があります。

  • 各ステートメントの先頭
  • 各ステートメントの終点 (§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)。
  • blockcheckeduncheckedifwhiledoforforeachlock または using ステートメントの終点の switch の確定代入状態は、そのステートメントの終点をターゲットとするすべての制御フロー転送の v の確定代入状態を確認することで決定します。 このようなすべての制御フロー転送で v が確定代入されている場合は、ステートメントの終点に v が確定代入されます。 それ以外の場合、v はステートメントの終点で確定代入されません。 考えられる制御フロー転送のセットは、ステートメントの到達可能性を確認する場合と同じ方法で決定されます (§13.2)。

: 到達不能ステートメントへの制御パスがないため、v は到達不能ステートメントの先頭で確定代入されます。 注釈

9.4.4.3 ブロックステートメント、checked および unchecked ステートメント

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

9.4.4.4 式ステートメント

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

  • v は、stmt の先頭と同じ expr の先頭の確定代入状態です。
  • v が、expr の終点で確定代入されている場合、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 は、stmt の先頭と同じ expr の先頭の確定代入状態です。
  • vexpr の終点で確定代入される場合、then_stmtへの制御フロー転送と、else_stmtまたは else 句がない場合、stmt の終点への制御フロー転送で確定代入されます。
  • 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 ステートメント

制御式 switch がある ステートメント stmt の場合:

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

ケースのガード句の先頭にある v の確定代入は、

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

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

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

終了サンプル

到達可能な switch ブロック ステートメント リストへの制御フロー転送の v の確定代入は、

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

これらのルールの結果として、switch_label で宣言されたパターン変数は、そのセクションの唯一の到達可能な switch ラベルでない場合、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;
           …
    }
}

終了サンプル

9.4.4.8 while ステートメント

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

while ( «expr» ) «while_body»
  • v は、stmt の先頭と同じ expr の先頭の確定代入状態です。
  • vexpr の最後で確定代入される場合、while_body および stmt の終点への制御フロー転送で確定代入されます。
  • vexpr の最後で "true 式の後の確定代入" 状態を維持している場合、while_body への制御フロー転送で確定代入されますが、stmt の終点では、確定代入されません。
  • vexpr の最後で "false 式の後の確定代入" 状態を維持している場合、stmt の終点への制御フロー転送で確定代入され、while_body への制御フロー転送では確定代入されません。

9.4.4.9 do ステートメント

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

do «do_body» while ( «expr» ) ;
  • v は、stmt の先頭から do_body への制御フロー転送で、stmt の先頭と同じ確定代入状態を維持します。
  • v は、expr の先頭で、do_body の終点と同じ確定代入状態を維持します。
  • vexpr の最後で確定代入される場合、stmt の終点への制御フロー転送で確定代入されます。
  • vexpr の最後で "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» ;
    }
}

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

9.4.4.11 Break、continue および goto ステートメント

break または continue ステートメントによる制御フロー転送の goto の確定代入状態は、そのステートメントの先頭の 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 の後
    • または、finally ステートメントを囲む try-finally または try-catch-finallyreturn ブロックの最後。

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

return ;
  • v が出力パラメーターである場合は、次のいずれかを確定代入する必要があります。
    • stmt の前
    • または、finally ステートメントを囲む try-finally または try-catch-finallyreturn ブロックの最後。

9.4.4.14 Try-catch ステートメント

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

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • try_block の先頭にある v の確定代入状態は、stmt の先頭にある v の確定代入状態と同じです。
  • catch_block_i (任意の i に対する) の先頭にある v の確定代入状態は、stmt の先頭にある v の確定代入状態と同じです。
  • stmt の終点にある v の確定代入状態は、vが、try_block の終点および各 catch_block_i (1 から n の各 i) で確定代入される場合 (および確定代入される場合のみ)、確定代入されるされます。

9.4.4.15 Try-finally ステートメント

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

try «try_block» finally «finally_block»
  • try_block の先頭にある v の確定代入状態は、stmt の先頭にある v の確定代入状態と同じです。
  • finally_block の先頭にある v の確定代入状態は、stmt の先頭にある v の確定代入状態と同じです。
  • stmt の最後にある v の確定代入状態は、少なくとも次のいずれかが true である場合 (および true である場合のみ) 確定代入されます。
    • v は、try_block の終点で確定代入されます。
    • v は、finally_block の終点で確定代入されます。

goto 内で開始する ステートメントなどの制御フロー転送が実行され、try_block 外で終了すると、vfinally_block の終点で確定代入された場合、v も制御フロー転送で確定代入されます。 (これは、v がこの制御フロー転送の別の理由で確定代入されている場合だけでなく、確定代入されていると見なされます)。

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

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

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

ステートメントが、try-finally ステートメントを囲む try-catch ステートメントn場合、確定代入分析が行われます。

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

終了サンプル

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

終了サンプル

false がある定数式の場合:

  • v が式の前で確定代入されている場合、v は式の後で確定代入されます。
  • それ以外の場合 v は、式の後で "true 式の後の確定代入" されます。

:

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

終了サンプル

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

9.4.4.22 単純式の一般的なルール

次の規則は、リテラル (§12.8.2)、単純名 (§12.8.4)、メンバー アクセス式 (§12.8.7)、インデックスのない基本アクセス式 (§12.8.7) の式に適用されます。 )、 typeof 式 (§12.8.18)、既定値の式 (§12.8.21)、 nameof 式 (§12.8.23)、宣言式 (§12.17)。

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

9.4.4.23 埋め込み式に関する一般的なルール

これらの式の種類には次のルールが適用されます: 括弧で囲んだ式 (§12.8.5)、タプル式 (§12.8.6)、要素アクセス式 (§12.8.12)、インデックス付きの基本アクセス式 (§12.8.15)、インクリメントおよびデクリメント式 (§12.8.16§12.9.6)、キャスト式 (§12.9.7)、単項式 +-~* 式、二項式 +-*/%<<>><<=>>===!=isas&|^ (§12.10§12.11§12.12§12.13)、複合代入式 (§12.21.4)、checked および unchecked 式 (§12.8.20)、配列生成式およびデリゲート生成式 (§12.8.17) およびawait 式 (§12.9.8)。

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

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

expr₁, expr₂, …, exprₓ の部分式を持つ expr 式の場合、次の順で評価されます。

  • expr₁ の先頭にある v の確定代入状態は、expr の先頭にある確定代入状態と同じです。
  • exprᵢ (1 より大きい i) の先頭にある v の確定代入状態は、exprᵢ₋₁ の最後にある確定代入状態と同じです。
  • 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 の確定代入状態は、exprv の状態と同じです。
  • 呼び出し式の場合、arg₁ 前の v の確定代入状態は、primary_expression の後の v の状態と同じです。
  • オブジェクト生成式の場合、arg₁ 前の v の確定代入状態は、expr の前の v の状態と同じです。
  • 引数 argᵢ の場合、argᵢ の後の v の確定代入状態は、通常の式ルールにより決定され、inout、 または ref 修飾子は無視されます。
  • 1 より大きい i の引数 argᵢ の場合、argᵢ の前の v の確定代入状態は、argᵢ₋₁ の後の v の状態と同じです。
  • 変数 v がが任意の引数で、out 引数 (つまり、"out v" の形式の引数) として渡された場合、expr の後の v の状態は、確定代入されます。 それ以外の場合、expr 後の v の状態は、argₓ 後の v の状態と同じです。
  • 配列初期化子 (§12.8.17.4)、オブジェクト初期化子 (§12.8.17.2.2)、コレクション初期化子 (§12.8.17.2.3)、匿名オブジェクト初期化子 (§12.8.17.3) の場合、確定代入状態は、これらのコンストラクトが定義される拡張によって決定されます。

9.4.4.25 単純代入式

e 内の代入対象のセットを次のように定義します。

  • e がタプル式の場合、e の代入対象は、e の要素の代入対象の和集合となります。
  • それ以外の場合、e の代入対象は 3 です。

形式の式 expr の場合:

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

: 次のコード例の内容:

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

x が 2 番目の単純な代入の左側として評価された後、変数 arr[x = 1] は、確定代入されたものとみなされます。

終了サンプル

9.4.4.26 && 式

形式の式 expr の場合:

«expr_first» && «expr_second»
  • expr_first の前の v の確定代入状態は、expr の前の v の確定代入状態と同じです。
  • expr_second の前の v の確定代入状態は、expr_firstv の状態が確定代入されたか、"true 式の後の確定代入" のいずれかの場合 (およびいずれかの場合のみ) 確定代入されます。 それ以外の場合は、確定代入されません。
  • expr の後の v の確定代入状態は、次によって決定されます。
    • expr_first の後の v の状態は、確定代入され、その後、exprv の状態が、確定代入されます。
    • それ以外の場合、expr_second の後の v の状態が確定代入され、expr_firstv の状態が、"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 ステートメントの埋め込みステートメントのいずれかで確定代入されますが、他では確定代入されません。 メソッド ifF ステートメントでは、変数 i が、最初の埋め込みステートメントで確定代入されます。これは、式 (i = y) の実行プログラムが、常に、この埋め込みステートメントの実行プログラムに先行するからです。 これに対し、変数 i は、2 番目の埋め込みステートメントでは確定代入されません。これは、x >= 0 が false をテストし、その結果、変数 i代入解除される可能性があるためです。

終了サンプル

9.4.4.27 || 式

形式の式 expr の場合:

«expr_first» || «expr_second»
  • expr_first の前の v の確定代入状態は、expr の前の v の確定代入状態と同じです。
  • expr_second の前の v の確定代入状態は、expr_firstv の状態が確定代入されたか、"true 式の後の確定代入" のいずれかの場合 (およびいずれかの場合のみ) 確定代入されます。 それ以外の場合は、確定代入されません。
  • expr の後の v の確定代入ステートメントは、次によって決定されます。
    • expr_first の後の v の状態は、確定代入され、その後、exprv の状態が、確定代入されます。
    • それ以外の場合、expr_second の後の v の状態が確定代入され、expr_firstv の状態が、"true 式の後の確定代入" となり、expr の後の v の状態が、確定代入されます。
    • それ以外の場合、expr_second の後の v の状態が、確定代入されるか、"true 式の後の確定代入" になり、その後、expr の後の v の状態が、"true 式の後の確定代入" となります。
    • それ以外の場合、expr_first の後の v の状態が、"true 式の後の確定代入" となり、expr_ second の前の 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 ステートメントの埋め込みステートメントのいずれかで確定代入されますが、他では確定代入されません。 メソッド ifG ステートメントでは、変数 i が、2 番目の埋め込みステートメントで確定代入されます。これは、式 (i = y) の実行プログラムが、常に、この埋め込みステートメントの実行プログラムに先行するからです。 これに対し、変数 i は、最初の埋め込みステートメントでは確定代入されません。これは、x >= 0 が true をテストし、その結果、変数 i代入解除される可能性があるためです。

終了サンプル

9.4.4.28 ! 式

形式の式 expr の場合:

! «expr_operand»
  • expr_operand の前の v の確定代入状態は、expr の前の v の確定代入状態と同じです。
  • expr の後の v の確定代入状態は、次によって決定されます。
    • v の後の の状態は、確定代入され、その後、v の状態が、確定代入されます。
    • それ以外の場合、v の後の の状態が、"false 式の後の確定代入" となり、v の状態が、"true 式の後の確定代入" となります。
    • それ以外の場合、v の後の の状態が、"true 式の後の確定代入" となり、expr の v の状態が、"false 式の後の確定代入" となります。
    • それ以外の場合、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) の場合、exprv の状態は、expr_secondv の状態と同じです。
    • それ以外の場合、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_condv の状態が確定代入されたか、"true 式の後の確定代入" のいずれかの場合、確定代入されます。
  • expr_false の前の v の確定代入状態は、expr_condv の状態が確定代入されたか、"false 式の後の確定代入" のいずれかの場合、確定代入されます。
  • expr の後の v の確定代入状態は、次によって決定されます。
    • expr_cond が、値 を持つ定数式 (true) の場合、exprv の状態は、expr_truev の状態と同じです。
    • それ以外の場合、expr_cond が、値 を持つ定数式 (false) の場合、exprv の状態は、expr_falsev の状態と同じです。
    • それ以外の場合、expr_true の後の v の状態が確定代入され、expr_falsev の状態が、確定代入され、その後、expr の後の v の状態が、確定代入されます。
    • それ以外の場合、expr の後の v の状態は、確定代入されません。

9.4.4.31 匿名関数

本文 (ブロックまたは) 付きの lambda_expression または anonymous_method_expressionexpr本文:

  • パラメータの確定代入は、名前付きメソッドのパラメータと同じです (§9.2.6§9.2.7§9.2.8)。
  • 本文の前の外部変数 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 が確定代入されていないため、コンパイル時エラーを生成します。

終了サンプル

: 例

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 の確定代入状態に影響しないため、コンパイル時エラーも生成されます。

終了サンプル

9.4.4.32 Throw 式

形式の式 expr の場合:

throw thrown_expr

  • thrown_expr の前の v の確定代入状態は、expr の前の v のと同じです。
  • exprv の確定代入状態は、"確定代入" です。

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

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

各ローカル関数の本文に対する確定代入は、呼び出し拠点ごとに個別に定義されます。 呼び出しのたびに、ローカル関数がキャプチャした変数は、呼び出し時点で確定代入された場合、確定代入されたものとみなされます。 この時点でローカル関数本文への制御フロー パスも存在し、到達可能とみなされます。 ローカル関数の呼び出し後、関数を離れるすべての制御ポイントで確定代入されたキャプチャ済み変数 (return ステートメント、yield ステートメント、await 式) は、呼び出し場所の後に確定代入されるとみなされます。

デリゲート変換には、ローカル関数本文への制御フロー パスがあります。 変換前にキャプチャされた変数が確定代入されている場合、本文にで確定代入されます。 ローカル関数が代入した変数は、変換後は代入されたものとしてみなされません。

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

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

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

終了サンプル

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 と呼ばれます。 注釈

9.6 変数参照の原子性

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

9.7 参照変数と戻り値

9.7.1 全般

reference variable は、リファレントと呼ばれる別の変数を参照する変数です (§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
    }     
}

終了サンプル

reference return は、returns-by-ref メソッドから返される variable_reference です (§15.6.1)。 この 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];
    }     
}

終了サンプル

9.7.2 ref-safe-context

9.7.2.1 全般

すべての参照変数は、参照変数の ref-safe-context がリファレントの ref-safe-context より大きくないことを保証する安全ルールに従います。

: セーフコンテキスト の関連概念は、関連する制約と共に (§16.4.15) で定義されています。 注釈

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

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

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

  • declaration-block: ローカル変数 (§9.2.9.1) への variable_reference の ref-safe-context は、そのスコープ内の入れ子になった埋め込みステートメントを含むローカル変数のスコープ (§13.6.2) です。

    ローカル変数への variable_reference は、参照変数がその変数の ref-safe-context 内で宣言されている場合にのみ、参照変数の有効なリファレントになります。

  • function-member: 関数内では、次のいずれかへの variable_reference は、function-member の ref-safe-context があります。

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

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

  • caller-context: 関数内では、以下のいずれかへの variable_reference に caller-context の ref-safe-context があります。

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

caller-context の ref-safe-context がある variable_reference は、参照の戻り値のリファレントにすることができます。

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

: 次のコードは、さまざまな ref-safe-context の例を示しています。 宣言は、ref 変数の初期化式になるリファレント の ref-safe-context を示しています。 次の例は、参照の戻り値の ref-safe-context を示しています。

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

サンプルの終了。

: struct 型の場合、暗黙的な this パラメーターは参照パラメーターとして渡されます。 function-member としての struct 型のフィールドの ref-safe-context では、これらのフィールドを参照の戻り値で返さないようにします。 このルールにより、次のコードが禁止されます。

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

サンプルの終了。

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 は caller-context です。 p が入力パラメーターの場合、書き込み可能な ref として返すことはできませんが、ref readonly として返すことができます。
  • p が出力パラメーターの場合、その ref-safe-context は caller-context です。
  • それ以外の場合、p が構造体型の this パラメーターである場合、その ref-safe-context は function-member になります。
  • それ以外の場合、パラメーターは値パラメーターであり、その ref-safe-context は function-member です。

9.7.2.4 フィールド ref-safe-context

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

  • e が参照型の場合、ref-safe-context は caller-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 は、次のコンテキストの生で最も狭くなります。

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

: 最後の箇条書きは、次のようなコードを処理するために必要です。

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

終了サンプル

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

9.7.2.7 値

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

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

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

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

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

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