次の方法で共有


12 式

12.1 全般

式は、演算子とオペランドのシーケンスです。 この句は、構文、オペランドと演算子の評価順序、および式の意味を定義します。

12.2 式の分類

12.2.1 全般

式の結果は、次のいずれかとして分類されます。

  • 値。 すべての値には、型が関連付けられています。
  • 変数。 特に指定しない限り、変数は明示的に型指定され、関連付けられた型 (つまり、変数の宣言された型) を持ちます。 暗黙的に型指定された変数には、関連付けられた型がありません。
  • null リテラル。 この分類を持つ式は、参照型または null 許容値型に暗黙的に変換できます。
  • 匿名関数。 この分類を持つ式は、互換性のあるデリゲート型または式ツリー型に暗黙的に変換できます。
  • タプル。 すべてのタプルには固定数の要素があり、それぞれに式とオプションのタプル要素名が付きます。
  • プロパティ へのアクセス。 すべてのプロパティ アクセスには、関連付けられた型 (つまり、プロパティの型) があります。 さらに、プロパティ アクセスには、インスタンス式が関連付けられている場合があります。 インスタンス プロパティ アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、 this で表されるインスタンスになります (§12.8.14)。
  • インデクサー アクセス。 すべてのインデクサー アクセスには、インデクサーの要素型という、関連付けられた型があります。 さらに、インデクサー アクセスには、関連付けられたインスタンス式と、関連付けられた引数リストがあります。 インデクサー アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果が this (§12.8.14) で表されるインスタンスになり、引数リストを評価した結果が呼び出しのパラメーター リストになります。
  • Nothing。 これは、式が戻り値の型が voidのメソッドの呼び出しである場合に発生します。 何も分類されない式は、 statement_expression (§13.7) または lambda_expression の本文 (§12.19) のコンテキストでのみ有効です。

大きな式の部分式として発生する式の場合は、注意が必要です。結果は次のいずれかとして分類することもできます。

  • 名前空間。 この分類を持つ式は、 member_access の左側 (§12.8.7) としてのみ表示できます。 その他のコンテキストでは、名前空間として分類された式によってコンパイル時エラーが発生します。
  • 型。 この分類を持つ式は、 member_access の左側 (§12.8.7) としてのみ表示できます。 その他のコンテキストでは、型として分類された式によってコンパイル時エラーが発生します。
  • メソッド グループ。メンバー参照 (§12.5) に起因するオーバーロードされたメソッドのセットです。 メソッド グループには、関連付けられたインスタンス式と、関連付けられた型引数リストが含まれる場合があります。 インスタンス メソッドが呼び出されると、インスタンス式を評価した結果は、 this で表されるインスタンスになります (§12.8.14)。 メソッド グループは、 invocation_expression (§12.8.10) または delegate_creation_expression (§12.8.17.6) で許可され、互換性のあるデリゲート型 (§10.8) に暗黙的に変換できます。 その他のコンテキストでは、メソッド グループとして分類された式によってコンパイル時エラーが発生します。
  • イベント アクセス。 すべてのイベント アクセスには、関連付けられた型 (つまり、イベントの型) があります。 さらに、イベント アクセスには、インスタンス式が関連付けられている場合があります。 イベント アクセスは、 += 演算子と -= 演算子 (§12.21.5) の左オペランドとして表示される場合があります。 その他のコンテキストでは、イベント アクセスとして分類された式によってコンパイル時エラーが発生します。 インスタンス イベント アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、 this で表されるインスタンスになります (§12.8.14)。
  • throw 式は、式内で例外をスローするために使用できるいくつかのコンテキストです。 スロー式は、任意の型への暗黙的な変換によって変換できます。

プロパティ アクセスまたはインデクサー アクセスは、get アクセサーまたは set アクセサーの呼び出しを実行することで、常に値として再分類されます。 特定のアクセサーは、プロパティまたはインデクサー アクセスのコンテキストによって決定されます。アクセスが割り当てのターゲットである場合、set アクセサーが呼び出されて新しい値が割り当てられます (§12.21.2)。 それ以外の場合は、get アクセサーが呼び出されて現在の値が取得されます (§12.2.2)。

instance アクセサーは、インスタンスのプロパティ アクセス、インスタンスでのイベント アクセス、またはインデクサー アクセスです。

12.2.2 式の値

式を含むコンストラクトのほとんどは、最終的に式が 値を表す必要があります。 このような場合、実際の式が名前空間、型、メソッド グループ、または何も示していない場合、コンパイル時エラーが発生します。 ただし、式がプロパティ アクセス、インデクサー アクセス、または変数を表す場合、プロパティ、インデクサー、または変数の値は暗黙的に置き換えられます。

  • 変数の値は、変数によって識別されるストレージの場所に現在格納されている値にすぎません。 変数は、値を取得する前に確実に代入 (§9.4) と見なされるか、それ以外の場合はコンパイル時エラーが発生します。
  • プロパティ アクセス式の値は、プロパティの get アクセサーを呼び出すことによって取得されます。 プロパティに get アクセサーがない場合は、コンパイル時エラーが発生します。 それ以外の場合は、関数メンバー呼び出し (§12.6.6) が実行され、呼び出しの結果がプロパティ アクセス式の値になります。
  • インデクサー アクセス式の値は、インデクサーの get アクセサーを呼び出すことによって取得されます。 インデクサーに get アクセサーがない場合は、コンパイル時エラーが発生します。 それ以外の場合は、インデクサー アクセス式に関連付けられた引数リストを使用して関数メンバー呼び出し (§12.6.6) が実行され、呼び出しの結果がインデクサー アクセス式の値になります。
  • タプル式の値は、タプル式の型に暗黙的なタプル変換 (§10.2.13) を適用することによって取得されます。 型を持たないタプル式の値を取得するとエラーになります。

12.3 静的および動的バインディング

12.3.1 全般

バインド は、式 (引数、オペランド、受信者) の型または値に基づいて、操作が何を参照するのかを決定するプロセスです。 たとえば、メソッド呼び出しのバインドは、受信側と引数の型に基づいて決定されます。 演算子のバインドは、そのオペランドの型に基づいて決定されます。

C# では、操作のバインドは通常、その部分式のコンパイル時の型に基づいてコンパイル時に決定されます。 同様に、式にエラーが含まれている場合は、エラーが検出され、コンパイラによって報告されます。 このアプローチは、 静的バインディングと呼ばれます。

ただし、式が 動的な式である場合 (つまり、型が dynamic) これは、参加するバインディングがコンパイル時の型ではなく実行時の型に基づいている必要があることを示します。 したがって、このような操作のバインドは、プログラムの実行中に操作が実行されるまで遅延されます。 これは、 ダイナミック バインディングと呼ばれます

操作が動的にバインドされている場合、コンパイラによってチェックはほとんどまたはまったく実行されません。 代わりに、実行時バインディングが失敗した場合、エラーは実行時に例外として報告されます。

C# での次の操作はバインディングの対象となります。

  • メンバー アクセス: e.M
  • メソッドの呼び出し: e.M(e₁,...,eᵥ)
  • デリゲート呼び出し: e(e₁,...,eᵥ)
  • 要素アクセス: e[e₁,...,eᵥ]
  • オブジェクトの作成: 新規 C(e₁,...,eᵥ)
  • オーバーロードされた単項演算子: +-! (論理否定のみ)、 ~++--truefalse
  • オーバーロードされた二項演算子: +-*/%&&& |||??^<<>>==!=><>=<=
  • 代入演算子: == ref+=-=*=/=%=&=|= ^=<<=>>=
  • 暗黙的な型変換と明示的な型変換

動的な式が関係しない場合、C# では既定で静的バインディングが使用されます。つまり、選択プロセスでコンパイル時の部分式の型が使用されます。 ただし、上記の操作の部分式の 1 つが動的な式である場合、操作は代わりに動的にバインドされます。

メソッド呼び出しが動的にバインドされ、受信側を含むパラメーターのいずれかが入力パラメーターである場合、コンパイル時エラーになります。

12.3.2 バインディング時間

静的バインディングはコンパイル時に行われますが、動的バインディングは実行時に行われます。 次のサブクラウスでは、 binding-time という用語は、バインドが行われるタイミングに応じて、コンパイル時または実行時を指します。

: 静的バインディングと動的バインディングとバインディング時間の概念を次に示します。

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

最初の 2 つの呼び出しは静的にバインドされます。 Console.WriteLine のオーバーロードは、引数のコンパイル時の型に基づいて選択されます。 したがって、バインディング時間は compile-time です。

3 番目の呼び出しは動的にバインドされます。 Console.WriteLine のオーバーロードは、その引数の実行時の型に基づいて選択されます。 これは、引数が動的な式であり、コンパイル時の型が動的であるために発生します。 したがって、3 番目の呼び出しのバインディング時間は run-time です。

end の例

12.3.3 動的バインディング

このサブクラスは有益です。

動的バインディングを使用すると、C# プログラムは動的オブジェクト(C# 型システムの通常のルールに従わないオブジェクト)と対話できます。 動的オブジェクトは、異なる型システムを持つ他のプログラミング言語のオブジェクトである場合もあれば、さまざまな操作に対して独自のバインド セマンティクスを実装するようにプログラムによって設定されるオブジェクトである場合もあります。

動的オブジェクトが独自のセマンティクスを実装するメカニズムは、実装定義です。 指定されたインターフェイス (再び実装定義) は、特別なセマンティクスがあることを C# ランタイムに通知するために、動的オブジェクトによって実装されます。 したがって、動的オブジェクトに対する操作が動的にバインドされるたびに、この仕様で指定されている C# ではなく、独自のバインド セマンティクスが引き継がれる。

動的バインディングの目的は動的オブジェクトとの相互運用を許可することですが、C# では、動的かどうかに関係なく、すべてのオブジェクトに対して動的バインディングを許可します。 これにより、動的オブジェクトに対する操作の結果自体が動的オブジェクトではない可能性がありますが、コンパイル時にプログラマにはまだ不明な型である可能性があるため、動的オブジェクトをよりスムーズに統合できます。 また、動的バインディングは、関係するオブジェクトが動的オブジェクトでない場合でも、エラーが発生しやすいリフレクション ベースのコードを排除するのに役立ちます。

12.3.4 部分式の種類

演算が静的にバインドされている場合、部分式の型 (受信者、引数、インデックス、オペランドなど) は、常にその式のコンパイル時の型と見なされます。

操作が動的にバインドされている場合、部分式の型は、部分式のコンパイル時の型に応じて異なる方法で決定されます。

  • コンパイル時型 dynamic の部分式は、式が実行時に評価する実際の値の型を持つと見なされます
  • コンパイル時の型が型パラメーターである部分式は、実行時に型パラメーターがバインドされる型を持つと見なされます
  • それ以外の場合、部分式はコンパイル時型と見なされます。

12.4 演算子

12.4.1 全般

"式" は "オペランド" と "演算子" で構成されます。 式の演算子は、オペランドに適用する演算を表します。

: 演算子の例としては、 +-*/newなどがあります。 オペランドの例としては、リテラル、フィールド、ローカル変数、式などがあります。 end の例

演算子には次の 3 種類があります。

  • 単項演算子。 単項演算子は 1 つのオペランドを受け取り、プレフィックス表記 ( –x など) または後置表記 ( x++ など) を使用します。
  • 二項演算子。 二項演算子は 2 つのオペランドを受け取り、すべて infix 表記 ( x + y など) を使用します。
  • 三項演算子。 1 つの三項演算子 ( ?:) のみが存在します。3 つのオペランドを受け取り、インフィックス表記 (c ? x : y) を使用します。

式内の演算子の評価順序は、演算子の 関連付け によって決まります (§12.4.2)。

式のオペランドは、左から右に評価されます。

: F(i) + G(i++) * H(i)では、メソッド Fi の古い値を使用して呼び出され、メソッド Gi の古い値で呼び出され、最後にメソッド H は新しい値 i で呼び出されます。 これは、演算子の優先順位とは別であり、関連性がありません。 end の例

特定の演算子はオーバーロードできます。 演算子オーバーロード (§12.4.3) を使用すると、一方または両方のオペランドがユーザー定義クラスまたは構造体型である操作に対して、ユーザー定義演算子の実装を指定できます。

12.4.2 演算子の優先順位と結合性

式に複数の演算子が含まれている場合、演算子の "優先順位" によって、個々の演算子を評価する順序が制御されます。

: たとえば、*演算子の方がバイナリ +演算子よりも優先順位が高いため、式x + y * zx + (y * z)として評価されます。 end note

演算子の優先順位は、関連付けられている文法プロダクションの定義によって決定されます。

: たとえば、additive_expressionは、+演算子または-演算子で区切られた一連のmultiplicative_expressionで構成されるため、+演算子と-演算子は、*/、および%演算子よりも優先順位が低くなります。 end note

: 次の表は、すべての演算子を優先順位の高い順にまとめたものです。

サブクラウス カテゴリ 演算子
§12.8 プライマリ x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 単項 + - !x ~ ++x --x (T)x await x
§12.10 乗法 * / %
§12.10 加法 + -
§12.11 Shift << >>
§12.12 関係式と型検査 < > <= >= is as
§12.12 等式 == !=
§12.13 論理 AND &
§12.13 論理 XOR ^
§12.13 論理和 \|
§12.14 条件 AND &&
§12.14 条件 OR \|\|
§12.15 および §12.16 null 結合とスロー式 ?? throw x
§12.18 条件 ?:
§12.21 および §12.19 代入とラムダ式 = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

end note

1 つのオペランドが同じ優先順位を持つ 2 つの演算子の間で発生した場合、演算子の結合性によって演算が実行される順序が決定されます。

  • 代入演算子と null 合体演算子を除き、すべての二項演算子は 左から右に行われます

    : x + y + z(x + y) + zとして評価されます。 end の例

  • 代入演算子、null 合体演算子、および条件演算子 (?:) は 右から連想。つまり、操作は右から左に実行されます。

    : x = y = zx = (y = z)として評価されます。 end の例

優先順位と結合性は、かっこを使用して制御することができます。

: x + y * z 最初に yz 乗算し、結果を xに追加しますが、 (x + y) * z 最初に xy を追加し、結果を z乗算します。 end の例

12.4.3 演算子のオーバーロード

単項演算子と二項演算子には、すべて定義済みの実装があります。 さらに、クラスと構造体に演算子宣言 (§15.10) を含めることで、ユーザー定義の実装を導入できます。 ユーザー定義演算子の実装は、定義済みの演算子の実装よりも常に優先されます。 §12.4.4 および §12.4.5 で説明されているように、該当するユーザー定義演算子の実装が存在しない場合にのみ、定義済みの演算子の実装が考慮されます。

読み込み可能な単項演算子は次のとおりです。

+ - ! (論理否定のみ) ~ ++ -- true false

: truefalse は式で明示的に使用されません (したがって、 §12.4.2 の優先順位テーブルには含まれません)、複数の式コンテキストで呼び出されるため、演算子と見なされます。s: ブール式 (§12.24) と条件 (§12.18) と条件論理演算子 (§12.14) を含む式。 end note

: null 許容演算子 (後置 !§12.8.9) はオーバーロード可能な演算子ではありません。 end note

読み込み可能な二項演算子は次のとおりです。

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

オーバーロードできるのは、上記の演算子だけです。 特に、メンバー アクセス、メソッド呼び出し、または =&&||???:=>checkeduncheckednewtypeofdefaultas、および is 演算子をオーバーロードすることはできません。

二項演算子がオーバーロードされると、対応する複合代入演算子 (存在する場合) も暗黙的にオーバーロードされます。

: 演算子 * のオーバーロードは、演算子 *=のオーバーロードでもあります。 これについては、 §12.21 で詳しく説明します。 end の例

代入演算子自体 (=) オーバーロードすることはできません。 代入では常に、変数への値の単純な格納が実行されます (§12.21.2)。

(T)xなどのキャスト操作は、ユーザー定義の変換 (§10.5) を提供することによってオーバーロードされます。

: ユーザー定義の変換は、 is 演算子または as 演算子の動作には影響しません。 end note

a[x]などの要素アクセスは、オーバーロード可能な演算子とは見なされません。 代わりに、ユーザー定義のインデックス作成はインデクサー (§15.9) によってサポートされます。

式では、演算子は演算子表記を使用して参照され、宣言では関数型表記を使用して演算子が参照されます。 次の表は、単項演算子と二項演算子の演算子と関数表記の関係を示しています。 最初のエントリでは、«op» はオーバーロード可能な単項プレフィックス演算子を表します。 2 番目のエントリでは、«op» は単項後置 ++ および -- 演算子を表します。 3 番目のエントリでは、«op» はオーバーロード可能なバイナリ演算子を表します。

: ++ 演算子と -- 演算子のオーバーロードの例については、 §15.10.2 を参照してください。 end note

演算子表記 機能表記
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

ユーザー定義の演算子宣言では、常に少なくとも 1 つのパラメーターが、演算子宣言を含むクラスまたは構造体型である必要があります。

: したがって、ユーザー定義演算子が定義済みの演算子と同じシグネチャを持つことはできません。 end note

ユーザー定義演算子の宣言では、演算子の構文、優先順位、または結合規則を変更できません。

: / 演算子は常に二項演算子であり、常に §12.4.2 で指定された優先順位レベルを持ち、常に左から連想されます。 end の例

: ユーザー定義演算子が必要な計算を実行することは可能ですが、直感的に予想される結果以外の結果を生成する実装は強くお勧めしません。 たとえば、演算子 == の実装では、2 つのオペランドの等価性を比較し、適切な bool 結果を返す必要があります。 end note

§12.9 から §12.21 までの個々の演算子の説明では演算子の定義済みの実装と、各演算子に適用される追加の規則を指定します。 この説明では、 非項演算子オーバーロードの解決binary 演算子のオーバーロードの解決数値の昇格、およびリフトされた演算子の定義を使用します。これらの定義は、次のサブクラスにあります。

12.4.4 単項演算子オーバーロードの解決

フォーム «op» x または x «op»の演算。«op» はオーバーロード可能な単項演算子で、 xX型の式であり、次のように処理されます。

  • 操作operator «op»(x)Xによって提供される候補ユーザー定義演算子のセットは、§12.4.6 の規則を使用して決定されます。
  • 候補のユーザー定義演算子のセットが空でない場合、これは操作の候補演算子のセットになります。 それ以外の場合、定義済みのバイナリ operator «op» 実装 (リフトされたフォームを含む) が、操作の候補演算子のセットになります。 特定の演算子の定義済みの実装は、演算子の説明で指定されます。 列挙型またはデリゲート型によって提供される定義済みの演算子は、バインディング時型 (null 許容型の場合は基になる型) が列挙型またはデリゲート型である場合にのみ、このセットに含まれます。
  • §12.6.4 のオーバーロード解決規則は、引数リスト(x)に関して最適な演算子を選択する候補演算子のセットに適用され、この演算子はオーバーロード解決プロセスの結果になります。 オーバーロードの解決で最適な演算子を 1 つ選択できない場合は、バインド時エラーが発生します。

12.4.5 二項演算子のオーバーロードの解決

フォーム x «op» yの演算。«op» はオーバーロード可能な二項演算子で、 xX型の式で、 yY型の式であり、次のように処理されます。

  • 操作operator «op»(x, y)XYによって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、 X によって提供される候補演算子と、 Yによって提供される候補演算子の和集合で構成され、それぞれ §12.4.6 の規則を使用して決定。 結合セットの場合、候補は次のようにマージされます。
    • XYが ID 変換可能な場合、またはXYが共通の基本型から派生している場合、共有候補演算子は結合セット内で 1 回だけ発生します。
    • XYの間に ID 変換がある場合、Yによって提供される演算子«op»Yは、Xによって提供される«op»Xと同じ戻り値の型を持ち、«op»Yのオペランド型は、«op»Xの対応するオペランド型への ID 変換を持つ場合、セット内で«op»Xのみ発生します。
  • 候補のユーザー定義演算子のセットが空でない場合、これは操作の候補演算子のセットになります。 それ以外の場合、定義済みのバイナリ operator «op» 実装 (リフトされたフォームを含む) が、操作の候補演算子のセットになります。 特定の演算子の定義済みの実装は、演算子の説明で指定されます。 定義済みの列挙型およびデリゲート演算子の場合、考慮される演算子は、いずれかのオペランドのバインド時型である列挙型またはデリゲート型によって提供される演算子のみです。
  • §12.6.4 のオーバーロード解決規則は、引数リスト(x, y)に関して最適な演算子を選択する候補演算子のセットに適用され、この演算子はオーバーロード解決プロセスの結果になります。 オーバーロードの解決で最適な演算子を 1 つ選択できない場合は、バインド時エラーが発生します。

12.4.6 ユーザー定義演算子候補

Tと操作operator «op»(A) («op» はオーバーロード可能な演算子、Aは引数リスト) を指定すると、演算子«op»(A)Tによって提供される候補ユーザー定義演算子のセットは次のように決定されます。

  • T₀型を決定します。 Tが null 許容値型の場合、T₀は基になる型です。それ以外の場合、T₀Tと等しくなります。
  • 引数リストAに対して少なくとも 1 つの演算子 (§12.6.4.2) が適用される場合、T₀のすべてのoperator «op»宣言と、そのような演算子のセットは、T₀の該当するすべての演算子で構成されます。
  • それ以外の場合、 T₀objectされている場合、候補の演算子のセットは空になります。
  • それ以外の場合、T₀によって提供される候補演算子のセットは、T₀の直接基底クラスによって提供される候補演算子のセット、またはT₀が型パラメーターの場合はT₀の有効な基底クラスです。

12.4.7 数値の昇格

12.4.7.1 全般

このサブクラスは有益です。

§12.4.7 とそのサブクラウスは、次の効果の組み合わせの概要です。

  • 暗黙的な数値変換の規則 (§10.2.3)
  • より良い変換のための規則 (§12.6.4.7);
  • 使用可能な算術演算子 (§12.10)、リレーショナル (§12.12)、整数論理 (§12.13.2) 演算子。

数値の昇格は、定義済みの単項および二項数値演算子のオペランドの特定の暗黙的な変換を自動的に実行することで構成されます。 数値の昇格は、明確なメカニズムではなく、定義済みの演算子にオーバーロード解決を適用する効果です。 数値の昇格は、特にユーザー定義演算子の評価には影響しませんが、同様の効果を示すためにユーザー定義演算子を実装できます。

数値の昇格の例として、バイナリ * 演算子の定義済みの実装を考えてみましょう。

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

この一連の演算子にオーバーロード解決規則 (§12.6.4) を適用すると、オペランド型から暗黙的な変換が存在する最初の演算子を選択する効果があります。

: bbyteで、sshortである操作b * sの場合、オーバーロードの解決では最適な演算子としてoperator *(int, int)が選択されます。 したがって、 bsintに変換され、結果の型が intされます。 同様に、iintddoubleである操作i * dの場合、overload解決では最適な演算子としてoperator *(double, double)が選択されます。 end の例

情報テキストの末尾。

12.4.7.2 単項数値の昇格

このサブクラスは有益です。

単項数値の昇格は、定義済みの +-、および ~ 単項演算子のオペランドに対して行われます。 単項数値の昇格は、単に型 sbytebyteshortushort、または char のオペランドを型 intに変換することで構成されます。 さらに、単項 - 演算子の場合、単項数値昇格は、 uint 型のオペランドを型 longに変換します。

情報テキストの末尾。

12.4.7.3 バイナリ数値の上位変換

このサブクラスは有益です。

2 項数値の昇格は、定義済みの +-*/%&|^==!=><>=、および <= 二項演算子のオペランドに対して行われます。 二項数値昇格は、両方のオペランドを共通の型に暗黙的に変換します。非関係演算子の場合は、演算の結果型にもなります。 バイナリ数値の昇格は、次の規則をここに表示される順序で適用することで構成されます。

  • いずれかのオペランドが decimal型の場合、もう一方のオペランドは型 decimalに変換されます。または、もう一方のオペランドが float 型または doubleの場合、バインド時エラーが発生します。
  • それ以外の場合、いずれかのオペランドが double型の場合、もう一方のオペランドは double型に変換されます。
  • それ以外の場合、いずれかのオペランドが float型の場合、もう一方のオペランドは float型に変換されます。
  • それ以外の場合、いずれかのオペランドが ulong型の場合、もう一方のオペランドは型 ulongに変換されます。または、もう一方のオペランドが type sbyteshortint、または longの場合、バインド時エラーが発生します。
  • それ以外の場合、いずれかのオペランドが long型の場合、もう一方のオペランドは long型に変換されます。
  • それ以外の場合、いずれかのオペランドが uint 型で、もう一方のオペランドが型 sbyteshort、または intの場合、両方のオペランドが型 longに変換されます。
  • それ以外の場合、いずれかのオペランドが uint型の場合、もう一方のオペランドは uint型に変換されます。
  • それ以外の場合、両方のオペランドが型 intに変換されます。

: 最初の規則では、 decimal 型と double 型と float 型を混在させる操作は許可されません。 この規則は、 decimal 型と double 型と float 型の間に暗黙的な変換が存在しないという事実に従います。 end note

: また、もう一方のオペランドが符号付き整数型の場合、オペランドが ulong 型であることはできません。 その理由は、符号付き整数型だけでなく、 ulong の全範囲を表すことができる整数型が存在しないためです。 end note

上記のどちらの場合も、キャスト式を使用して、一方のオペランドを、もう一方のオペランドと互換性のある型に明示的に変換できます。

: 次のコード内

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

decimaldoubleを乗算できないため、バインド時エラーが発生します。 エラーは、次のように 2 番目のオペランドを明示的に decimal に変換することで解決されます。

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

end の例

情報テキストの末尾。

12.4.8 リフト演算子

リフト演算子 、null 非許容値型に対して動作する定義済みの演算子とユーザー定義演算子を、それらの型の null 許容形式でも使用できます。 リフト演算子は、次に示すように、特定の要件を満たす定義済みおよびユーザー定義の演算子から構築されます。

  • 単項演算子 +++---!(論理否定)、および ~の場合、オペランドと結果の型の両方が null 非許容値型の場合、リフトされた形式の演算子が存在します。 リフトされたフォームは、オペランドと結果の型に 1 つの ? 修飾子を追加することによって構築されます。 オペランドがnullされている場合、リフトされた演算子はnull値を生成します。 それ以外の場合、リフトされた演算子はオペランドのラップを解除し、基になる演算子を適用して結果をラップします。
  • 二項演算子 +-*/%&|^<<、および >>の場合、オペランドと結果の型がすべて null 非許容値型の場合、リフトされた形式の演算子が存在します。 リフトされたフォームは、各オペランドと結果の型に 1 つの ? 修飾子を追加することによって構築されます。 リフトされた演算子は、一方または両方のオペランドがnull場合にnull値を生成します (例外は、§12.13.5 で説明されているように、bool?型の&演算子と|演算子)。 それ以外の場合、リフトされた演算子はオペランドのラップを解除し、基になる演算子を適用して、結果をラップします。
  • 等値演算子 == および !=の場合、オペランド型が null 非許容値型であり、結果の型が bool場合は、リフトされた形式の演算子が存在します。 リフトされた形式は、各オペランド型に単一の ? 修飾子を追加することによって構築されます。 リフトされた演算子では、2 つの null 値が等しく、 null 値がnull 以外の値と等しくないと見なされます。 両方のオペランドがnullでない場合、リフトされた演算子はオペランドをラップ解除し、基になる演算子を適用して bool 結果を生成します。
  • 関係演算子 <><=、および >=の場合、オペランドの型が null 非許容値型であり、結果の型が bool場合は、リフトされた形式の演算子が存在します。 リフトされた形式は、各オペランド型に単一の ? 修飾子を追加することによって構築されます。 リフトされた演算子は、一方または両方のオペランドがnull場合にfalse値を生成します。 それ以外の場合、リフトされた演算子はオペランドをラップ解除し、基になる演算子を適用して bool 結果を生成します。

12.5 メンバー参照

12.5.1 全般

メンバー参照とは、型のコンテキストにおける名前の意味が決定されるプロセスです。 メンバー参照は、式の simple_name (§12.8.4) または member_access (§12.8.7) の評価の一環として行うことができます。 invocation_expressionprimary_expressionとしてsimple_nameまたはmember_accessが発生した場合 (§12.8.10.2)、メンバーはに割り当てられている

メンバーがメソッドまたはイベントである場合、またはデリゲート型 (§20) または型 dynamic (§8.2.4) の定数、フィールド、またはプロパティである場合、メンバーは 使用可能であると言われます。

メンバー参照では、メンバーの名前だけでなく、メンバーが持つ型パラメーターの数と、メンバーにアクセスできるかどうかを考慮します。 メンバー参照の目的上、ジェネリック メソッドと入れ子になったジェネリック型には、それぞれの宣言で示される型パラメーターの数が含まれており、他のすべてのメンバーには 0 個の型パラメーターがあります。

Tの型引数K持つ名前Nのメンバー検索は、次のように処理されます。

  • まず、 N という名前のアクセス可能なメンバーのセットが決定されます。
    • Tが型パラメーターの場合、セットは、Tのプライマリ制約またはセカンダリ制約 (§15.2.5) として指定された各型でNという名前のアクセス可能なメンバーのセットと、objectNという名前のアクセス可能なメンバーのセットの和集合です。
    • それ以外の場合、セットは、継承されたメンバーと object 内の N という名前のアクセス可能なメンバーを含め、TN という名前のすべてのアクセス可能な (§7.5) メンバーで構成されます。 Tが構築された型の場合、メンバーのセットは、§15.3.3 で説明されているように型引数を置き換えることによって取得されます。 override修飾子を含むメンバーは、セットから除外されます。
  • 次に、 K が 0 の場合、型パラメーターを含む宣言を持つ入れ子になった型はすべて削除されます。 Kが 0 でない場合は、型パラメーターの数が異なるメンバーがすべて削除されます。 Kが 0 の場合、型推論プロセス (§12.6.3) が型引数を推論できる可能性があるため、型パラメーターを持つメソッドは削除されません。
  • 次に、メンバーが呼び出されると、呼び出し不可能なすべてのメンバーがセットから削除されます。
  • 次に、他のメンバーによって非表示になっているメンバーがセットから削除されます。 Sはメンバー Mが宣言されている型であるセット内のすべてのメンバー S.Mに対して、次の規則が適用されます。
    • Mが定数、フィールド、プロパティ、イベント、または列挙メンバーの場合、基本型のSで宣言されているすべてのメンバーがセットから削除されます。
    • Mが型宣言の場合、基本型のSで宣言されているすべての非型がセットから削除され、基本型のSで宣言Mと同じ数の型パラメーターを持つすべての型宣言がセットから削除されます。
    • Mがメソッドの場合、基本型のSで宣言されているすべての非メソッド メンバーがセットから削除されます。
  • 次に、クラス メンバーによって非表示になっているインターフェイス メンバーがセットから削除されます。 この手順は、 T が型パラメーターであり、 Tobject 以外の有効な基底クラスと空でない有効なインターフェイス セット (§15.2.5) の両方がある場合にのみ有効です。 Sはメンバー Mが宣言されている型であるセット内のすべてのメンバーS.Mに対して、Sobject以外のクラス宣言である場合に、次の規則が適用されます。
    • Mが定数、フィールド、プロパティ、イベント、列挙型メンバー、または型宣言の場合、インターフェイス宣言で宣言されているすべてのメンバーがセットから削除されます。
    • Mがメソッドの場合、インターフェイス宣言で宣言されているすべての非メソッド メンバーがセットから削除され、インターフェイス宣言で宣言されたMと同じシグネチャを持つすべてのメソッドがセットから削除されます。
  • 最後に、非表示のメンバーを削除すると、検索の結果が決定されます。
    • セットがメソッドではない 1 つのメンバーで構成されている場合、このメンバーは参照の結果になります。
    • それ以外の場合、セットにメソッドのみが含まれている場合、このメソッドのグループは参照の結果になります。
    • それ以外の場合、参照があいまいになり、バインド時エラーが発生します。

型パラメーターとインターフェイス以外の型のメンバー参照と、厳密に単一継承であるインターフェイスのメンバー参照 (継承チェーン内の各インターフェイスには、0 個または 1 つの直接基本インターフェイスがある) の場合、参照規則の効果は、派生メンバーが同じ名前またはシグネチャを持つ基本メンバーを非表示にするだけです。 このような単一継承参照があいまいになることはありません。 多重継承インターフェイスでのメンバー参照によって生じる可能性があるあいまいさは、 §18.4.6 で説明されています。

: このフェーズでは、1 種類のあいまいさのみが考慮されます。 メンバー参照の結果がメソッド グループに含まれる場合は、§12.6.4.1、§12.6.6.1 で説明されているように、あいまいさのためにメソッド グループの使用がさらに失敗する可能性があります。 end note

12.5.2 基本型

メンバー参照の目的で、 T 型は次の基本型を持つと見なされます。

  • Tobjectまたはdynamicの場合、Tは基本型を持っていません。
  • Tenum_typeの場合、Tの基本型は、System.EnumSystem.ValueType、およびobjectのクラス型です。
  • Tstruct_typeの場合、Tの基本型はSystem.ValueTypeおよびobjectクラス型です。

    : nullable_value_typestruct_type です (§8.3.1)。 end note

  • Tclass_typeの場合、Tの基本型は、クラス型objectを含むTの基底クラスです。
  • Tinterface_typeの場合、Tの基本型はTの基本インターフェイスであり、クラス型object
  • Tarray_typeの場合、Tの基本型はSystem.Arrayおよびobjectクラス型です。
  • Tdelegate_typeの場合、Tの基本型はSystem.Delegateおよびobjectクラス型です。

12.6 関数メンバー

12.6.1 全般

関数メンバーは、実行可能ステートメントを含むメンバーです。 関数メンバーは常に型のメンバーであり、名前空間のメンバーにすることはできません。 C# では、関数メンバーの次のカテゴリが定義されています。

  • メソッド
  • プロパティ
  • Events
  • インデクサー
  • ユーザー定義の演算子
  • インスタンス コンストラクター
  • 静的コンストラクター
  • ファイナライザー

ファイナライザーと静的コンストラクター (明示的に呼び出すことができない) を除き、関数メンバーに含まれるステートメントは関数メンバー呼び出しによって実行されます。 関数メンバー呼び出しを記述するための実際の構文は、特定の関数メンバー カテゴリによって異なります。

関数メンバー呼び出しの引数リスト (§12.6.2) は、関数メンバーのパラメーターの実際の値または変数参照を提供します。

ジェネリック メソッドの呼び出しでは、型推論を使用して、メソッドに渡す型引数のセットを決定できます。 このプロセスについては、 §12.6.3 で説明します。

メソッド、インデクサー、演算子、およびインスタンス コンストラクターの呼び出しでは、オーバーロード解決を使用して、呼び出す関数メンバーの候補セットを決定します。 このプロセスについては、 §12.6.4 で説明されています。

バインディング時に特定の関数メンバーが特定されると (場合によってはオーバーロード解決を通じて)、関数メンバーを呼び出す実際のランタイム プロセスについては、 §12.6.6 で説明します。

: 次の表は、明示的に呼び出すことができる 6 つのカテゴリの関数メンバーを含むコンストラクトで行われる処理をまとめたものです。 表では、 exy、および value は、変数または値として分類される式を示 TF はメソッドの単純な名前、 P はプロパティの単純な名前です。

構造体 説明
メソッドの呼び出し F(x, y) オーバーロードの解決は、包含クラスまたは構造体で F 最適なメソッドを選択するために適用されます。 このメソッドは、引数リスト (x, y)を使用して呼び出されます。 メソッドが staticされていない場合、インスタンス式は this
T.F(x, y) オーバーロードの解決は、クラスまたは構造体のTF最適なメソッドを選択するために適用されます。 メソッドが staticされていない場合は、バインド時エラーが発生します。 このメソッドは、引数リスト (x, y)を使用して呼び出されます。
e.F(x, y) オーバーロードの解決は、eの型によって指定されたクラス、構造体、またはインターフェイスでF最適なメソッドを選択するために適用されます。 メソッドが static場合、バインド時エラーが発生します。 このメソッドは、インスタンス式 e で呼び出され、引数リスト (x, y)
「プロパティ アクセス」 P 包含クラスまたは構造体内のプロパティ P の get アクセサーが呼び出されます。 Pが書き込み専用の場合、コンパイル時エラーが発生します。 Pstaticされていない場合、インスタンス式はthis
P = value 包含クラスまたは構造体のプロパティ P の set アクセサーは、引数リスト (value)を使用して呼び出されます。 Pが読み取り専用の場合、コンパイル時エラーが発生します。 Pstaticされていない場合、インスタンス式はthis
T.P クラスまたは構造体T内のプロパティ Pの get アクセサーが呼び出されます。 コンパイル時エラーは、 Pstatic されていない場合、または P が書き込み専用の場合に発生します。
T.P = value クラスまたは構造体T内のプロパティ Pの set アクセサーは、引数リスト (value)を使用して呼び出されます。 Pstaticされていない場合、またはPが読み取り専用の場合、コンパイル時エラーが発生します。
e.P Eの型によって指定されたクラス、構造体、またはインターフェイス内のプロパティ Pの get アクセサーは、インスタンス式eで呼び出されます。 バインド時エラーは、 Pstatic されている場合、または P が書き込み専用の場合に発生します。
e.P = value Eの型によって指定されたクラス、構造体、またはインターフェイスのプロパティ Pの set アクセサーは、インスタンス式eと引数リスト(value)で呼び出されます。 バインド時エラーは、 Pstatic 場合、または P が読み取り専用の場合に発生します。
イベント アクセス E += value 包含クラスまたは構造体内のイベント E の追加アクセサーが呼び出されます。 Estaticされていない場合、インスタンス式はthis
E -= value 包含クラスまたは構造体内のイベント E の remove アクセサーが呼び出されます。 Estaticされていない場合、インスタンス式はthis
T.E += value クラスまたは構造体T内のイベント Eの追加アクセサーが呼び出されます。 Estaticされていない場合、バインド時エラーが発生します。
T.E -= value クラスまたは構造体T内のイベント Eの remove アクセサーが呼び出されます。 Estaticされていない場合、バインド時エラーが発生します。
e.E += value Eの型によって指定されたクラス、構造体、またはインターフェイス内のイベント Eの追加アクセサーは、インスタンス式eで呼び出されます。 Estaticされると、バインド時エラーが発生します。
e.E -= value Eの型によって指定されたクラス、構造体、またはインターフェイス内のイベント Eの remove アクセサーは、インスタンス式eで呼び出されます。 Estaticされると、バインド時エラーが発生します。
インデクサーへのアクセス e[x, y] オーバーロードの解決は、 eの型によって指定されたクラス、構造体、またはインターフェイスで最適なインデクサーを選択するために適用されます。 インデクサーの get アクセサーは、インスタンス式 e で呼び出され、引数リスト (x, y)。 インデクサーが書き込み専用の場合、バインド時エラーが発生します。
e[x, y] = value オーバーロードの解決は、 eの型によって指定されたクラス、構造体、またはインターフェイスで最適なインデクサーを選択するために適用されます。 インデクサーの set アクセサーは、インスタンス式 e と引数リスト (x, y, value)で呼び出されます。 インデクサーが読み取り専用の場合、バインド時エラーが発生します。
演算子の呼び出し -x オーバーロードの解決は、 xの型によって指定されたクラスまたは構造体で最適な単項演算子を選択するために適用されます。 選択した演算子は、引数リスト (x)で呼び出されます。
x + y オーバーロードの解決は、 xyの型によって指定されるクラスまたは構造体で最適な二項演算子を選択するために適用されます。 選択した演算子は、引数リスト (x, y)で呼び出されます。
インスタンス コンストラクターの呼び出し new T(x, y) オーバーロードの解決は、クラスまたは構造体の Tで最適なインスタンス コンストラクターを選択するために適用されます。 インスタンス コンストラクターは、引数リスト (x, y)を使用して呼び出されます。

end note

12.6.2 引数リスト

12.6.2.1 全般

すべての関数メンバーとデリゲートの呼び出しには、関数メンバーのパラメーターの実際の値または変数参照を提供する引数リストが含まれています。 関数メンバー呼び出しの引数リストを指定するための構文は、関数メンバー のカテゴリによって異なります。

  • インスタンス コンストラクター、メソッド、インデクサー、デリゲートの場合、引数は次に示すように、 argument_listとして指定されます。 インデクサーの場合、set アクセサーを呼び出すとき、引数リストには、代入演算子の右オペランドとして指定された式も追加で含まれます。

    : この追加引数は、set アクセサーの呼び出し中にだけ、オーバーロードの解決には使用されません。 end note

  • プロパティの場合、get アクセサーを呼び出すときに引数リストが空になり、set アクセサーを呼び出すときに代入演算子の右オペランドとして指定された式で構成されます。
  • イベントの場合、引数リストは、 += または -= 演算子の右オペランドとして指定された式で構成されます。
  • ユーザー定義演算子の場合、引数リストは単項演算子の単一オペランドまたは二項演算子の 2 つのオペランドで構成されます。

プロパティ (§15.7) とイベント (§15.8) の引数は常に値パラメーター (§15.6.2.2) として渡されます。 ユーザー定義演算子 (§15.10) の引数は、常に値パラメーター (§15.6.2.2) または入力パラメーター (§9.2.8) として渡されます。 インデクサー (§15.9) の引数は、常に値パラメーター (§15.6.2.2)、入力パラメーター (§9.2.8)、またはパラメーター配列 (§15.6.2.4) として渡されます。 関数メンバーのこれらのカテゴリでは、出力パラメーターと参照パラメーターはサポートされていません。

インスタンス コンストラクター、メソッド、インデクサー、またはデリゲート呼び出しの引数は、 argument_listとして指定されます。

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

argument_listは、コンマで区切られた 1 つ以上のargumentで構成されます。 各引数は、省略可能な argument_name の後に argument_valueで構成されます。 argument_nameを持つargument名付き引数と呼ばれますがargument_nameのないargumentは位置引数です

argument_valueには、次のいずれかの形式を使用できます。

  • 。引数が値パラメーターとして渡されるか、入力パラメーターに変換された後、(§12.6.4.2 によって決定され、§12.6.2.3 で説明されているように渡
  • キーワードin、引数が入力パラメーター (§15.6.2.3.2) として渡されることを示すvariable_reference (§9.5) が続きます。 変数を入力パラメーターとして渡す前に、変数を確実に割り当てる必要があります (§9.4)。
  • キーワード ref 続けて variable_reference (§9.5)、引数が参照パラメーターとして渡されることを示します (§15.6.2.3.3)。 変数を参照パラメーターとして渡す前に、変数を確実に割り当てる必要があります (§9.4)。
  • キーワード out 続けて variable_reference (§9.5)、引数が出力パラメーターとして渡されることを示します (§15.6.2.3.4)。 変数が出力パラメーターとして渡される関数メンバー呼び出しの後、変数は確実に割り当てられていると見なされます (§9.4)。

このフォームは、引数 valueinputreference、または outputパラメーター受け渡しモードを決定します。 ただし、前述のように、値渡しモードの引数は、入力パッシング モードで 1 つに変換される可能性があります。

揮発性フィールド (§15.5.4) を入力、出力、または参照パラメーターとして渡すと、呼び出されたメソッドによってフィールドが揮発性として扱われないため、警告が発生します。

12.6.2.2 対応するパラメーター

引数リスト内の引数ごとに、呼び出される関数メンバーまたはデリゲートに対応するパラメーターが必要です。

次に示すパラメーター リストは、次のように決定されます。

  • クラスで定義されている仮想メソッドとインデクサーの場合、パラメーター リストは、レシーバーの静的型から開始し、その基底クラスを検索するときに検出された関数メンバーの最初の宣言またはオーバーライドから選択されます。
  • 部分メソッドの場合、定義する部分メソッド宣言のパラメーター リストが使用されます。
  • 他のすべての関数メンバーとデリゲートには、使用されるパラメーター リストが 1 つだけあります。

引数またはパラメーターの位置は、引数リストまたはパラメーター リストの前にある引数またはパラメーターの数として定義されます。

関数メンバー引数の対応するパラメーターは、次のように設定されます。

  • インスタンス コンストラクター、メソッド、インデクサー、デリゲートの argument_list 内の引数:
    • パラメーターがパラメーター リスト内の同じ位置に存在する位置引数は、パラメーターがパラメーター配列であり、関数メンバーがその展開された形式で呼び出されない限り、そのパラメーターに対応します。
    • 展開された形式で呼び出されたパラメーター配列を持つ関数メンバーの位置引数。パラメーター リスト内のパラメーター配列の位置以降に発生し、パラメーター配列内の要素に対応します。
    • 名前付き引数は、パラメーター リスト内の同じ名前のパラメーターに対応します。
    • インデクサーの場合、set アクセサーを呼び出すとき、代入演算子の右オペランドとして指定された式は、set アクセサー宣言の暗黙的な value パラメーターに対応します。
  • プロパティの場合、get アクセサーを呼び出すときに引数はありません。 set アクセサーを呼び出すとき、代入演算子の右オペランドとして指定された式は、set アクセサー宣言の暗黙的な値パラメーターに対応します。
  • ユーザー定義の単項演算子 (変換を含む) の場合、単一オペランドは演算子宣言の単一パラメーターに対応します。
  • ユーザー定義の二項演算子の場合、左オペランドは最初のパラメーターに対応し、右側のオペランドは演算子宣言の 2 番目のパラメーターに対応します。
  • 名前のない引数は、パラメーター配列に対応する位置外の名前付き引数または名前付き引数の後にある場合、パラメーターに対応しません。

    : これにより、M(c: false, valueB);によってvoid M(bool a = true, bool b = true, bool c = true);が呼び出されなくなります。 最初の引数は範囲外で使用されます (引数は最初の位置で使用されますが、 c という名前のパラメーターは 3 番目の位置にあります)、次の引数に名前を付ける必要があります。 つまり、末尾以外の名前付き引数は、名前と位置が同じ対応するパラメーターを検索する場合にのみ許可されます。 end note

12.6.2.3 引数リストの実行時評価

関数メンバー呼び出し (§12.6.6) の実行時に、引数リストの式または変数参照は、次のように左から右に順番に評価されます。

  • パラメーターの受け渡しモードが value の場合、値引数の場合

    • 引数式が評価され、対応するパラメーター型への暗黙的な変換 (§10.2) が実行されます。 結果の値は、関数メンバー呼び出しの value パラメーターの初期値になります。

    • それ以外の場合は、パラメーターの受け渡しモードが入力されます。 引数が変数参照であり、引数の型とパラメーターの型の間に ID 変換 (§10.2.2) が存在する場合、結果の格納場所は関数メンバー呼び出しのパラメーターによって表される格納場所になります。 それ以外の場合は、対応するパラメーターと同じ型でストレージの場所が作成されます。 引数式が評価され、対応するパラメーター型への暗黙的な変換 (§10.2) が実行されます。 結果の値は、そのストレージの場所に格納されます。 そのストレージの場所は、関数メンバー呼び出しの入力パラメーターによって表されます。

      : 次の宣言とメソッド呼び出しが考えられます。

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      M1(i) メソッド呼び出しでは、i自体が入力引数として渡されます。これは、変数として分類され、入力パラメーターと同じ型intがあるためです。 M1(i + 5) メソッド呼び出しでは、名前のないint変数が作成され、引数の値で初期化された後、入力引数として渡されます。 §12.6.4.2 および §12.6.4.4 を参照

      end の例

  • 入力、出力、または参照引数の場合、変数参照が評価され、結果の格納場所が関数メンバー呼び出しのパラメーターによって表される格納場所になります。 入力または参照引数の場合、変数はメソッド呼び出しの時点で確実に割り当てられます。 変数参照が出力引数として指定されている場合、または reference_typeの配列要素である場合は、実行時チェックが実行され、配列の要素型がパラメーターの型と同じであることを確認します。 このチェックが失敗すると、 System.ArrayTypeMismatchException がスローされます。

: この実行時チェックは、配列の共変性 (§17.6) が原因で必要です。 end note

: 次のコード内

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

Fの 2 番目の呼び出しでは、bの実際の要素型がstringされ、objectされていないため、System.ArrayTypeMismatchExceptionがスローされます。

end の例

メソッド、インデクサー、およびインスタンス コンストラクターは、その右端のパラメーターをパラメーター配列として宣言できます (§15.6.2.4)。 このような関数メンバーは、該当する (§12.6.4.2) に応じて、通常の形式または展開された形式で呼び出されます。

  • パラメーター配列を持つ関数メンバーが通常の形式で呼び出されると、パラメーター配列に指定される引数は、パラメーター配列型に暗黙的に変換可能な単一の式 (§10.2) になります。 この場合、パラメーター配列は値パラメーターとまったく同じように動作します。
  • パラメーター配列を持つ関数メンバーが展開形式で呼び出されると、呼び出しでは、パラメーター配列に対して 0 個以上の位置引数を指定する必要があります。各引数は、パラメーター配列の要素型に暗黙的に変換可能な式 (§10.2) です。 この場合、呼び出しは引数の数に対応する長さのパラメーター配列型のインスタンスを作成し、指定された引数値を使用して配列インスタンスの要素を初期化し、新しく作成された配列インスタンスを実際の引数として使用します。

引数リストの式は、常にテキスト順に評価されます。

: したがって、例

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

この例では、次のように出力されます。

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

end の例

パラメーター配列を持つ関数メンバーが展開された形式で少なくとも 1 つの展開された引数で呼び出されると、展開された引数の周囲に配列初期化子 (§12.8.17.5) を持つ配列作成式が挿入されたかのように呼び出しが処理されます。 パラメーター配列の引数がない場合は、空の配列が渡されます。渡された参照が新しく割り当てられた空の配列であるか、既存の空の配列であるかは指定されていません。

: 宣言を指定する

void F(int x, int y, params object[] args);

メソッドの拡張形式の次の呼び出し

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

に正確に対応する

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

end の例

対応する省略可能なパラメーターを持つ関数メンバーから引数を省略すると、関数メンバー宣言の既定の引数が暗黙的に渡されます。 (これには、前述のように、ストレージの場所の作成が含まれる場合があります)。

: これらは常に定数であるため、その評価は残りの引数の評価には影響しません。 end note

12.6.3 型推論

12.6.3.1 全般

型引数を指定せずにジェネリック メソッドを呼び出すと、 型推論 プロセスは呼び出しの型引数の推論を試みます。 型推論が存在すると、ジェネリック メソッドを呼び出すためにより便利な構文を使用でき、プログラマは冗長な型情報を指定しないようにすることができます。

例:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

型推論により、型引数 int および string は、引数からメソッドに対して決定されます。

end の例

型推論は、メソッド呼び出しのバインド時処理 (§12.8.10.2) の一部として発生し、呼び出しのオーバーロード解決ステップの前に行われます。 メソッドの呼び出しで特定のメソッド グループが指定されていて、メソッド呼び出しの一部として型引数が指定されていない場合、メソッド グループ内の各ジェネリック メソッドに型推論が適用されます。 型の推論が成功した場合、推論された型引数を使用して、後続のオーバーロード解決のための引数の型が決定されます。 オーバーロード解決でジェネリック メソッドを呼び出すメソッドとして選択した場合、推論された型引数が呼び出しの型引数として使用されます。 特定のメソッドの型推論が失敗した場合、そのメソッドはオーバーロードの解決に関与しません。 型推論自体が失敗しても、バインド時エラーは発生しません。 ただし、オーバーロードの解決で該当するメソッドが見つからない場合、バインド時エラーが発生することがよくあります。

指定された各引数がメソッド内の 1 つのパラメーター (§12.6.2.2) に完全に対応していない場合、または対応する引数のない省略可能でないパラメーターがある場合、推論はすぐに失敗します。 それ以外の場合は、ジェネリック メソッドに次のシグネチャがあるとします。

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

フォームM(E₁ ...Eₓ)メソッド呼び出しでは、型推論のタスクは、呼び出しM<S₁...Sᵥ>(E₁...Eₓ)が有効になるようにX₁...Xᵥ各型パラメーターに対してS₁...Sᵥ一意の型引数を検索することです。

型推論のプロセスをアルゴリズムとして以下に説明します。 準拠コンパイラは、すべてのケースで同じ結果に達した場合に、代替アプローチを使用して実装できます。

推論のプロセス中に、各型パラメーターXᵢは、特定の型Sᵢ修正されるか関連付けられた一連のbounds.各境界は、何らかの型のTです。 最初に、各型変数 Xᵢ は、空の境界セットで固定解除されます。

型の推論は段階的に行われます。 各フェーズでは、前のフェーズの結果に基づいて、より多くの型変数の型引数を推論しようとします。 最初のフェーズでは境界のいくつかの初期推論が行われますが、2 番目のフェーズでは型変数が特定の型に修正され、さらに境界が推論されます。 2 番目のフェーズは、何度も繰り返す必要がある場合があります。

: 型推論は、メソッド グループの変換 (§12.6.3.14) や一連の式の最も一般的な型 (§12.6.3.15) の検索など、他のコンテキストでも使用されます。 end note

12.6.3.2 第 1 フェーズ

各メソッド引数 Eᵢ:

  • Eᵢが匿名関数の場合、explicit パラメーター型推論 (§12.6.3.8) がfromEᵢ Tᵢ
  • それ以外の場合、Eᵢに型Uがあり、対応するパラメーターが値パラメーター (§15.6.2.2) の場合、lower バインド推論 (§12.6.3.10) はU Tᵢからされます。
  • それ以外の場合、 Eᵢ に型 U があり、対応するパラメーターが参照パラメーター (§15.6.2.3.3) または出力パラメーター (§15.6. 2.3.4) の後に、 例の推論 (§12.6.3.9) が されますU toTᵢ
  • それ以外の場合、Eᵢに型Uがあり、対応するパラメーターが入力パラメーター (§15.6.2.3.2) であり、Eᵢが入力引数である場合は、exact 推論 (§12.6.3.9) がU から されますTᵢ
  • それ以外の場合、Eᵢに型Uがあり、対応するパラメーターが入力パラメーター (§15.6.2.3.2) の場合、lower バインド推論 (§12.6.3.10) がfromU Tᵢになります。
  • それ以外の場合、この引数の推論は行われません。

12.6.3.3 第2フェーズ

2 番目のフェーズは次のように進みます。

  • すべての修正されていない型変数Xᵢ (§12.6.3.6) Xₑはすべて固定されます (§12.6.3.12)。
  • このような型変数が存在しない場合、 unfixed 型変数 Xᵢ はすべて 修正され 次のすべてが保持されます。
    • 1 つ以上の型変数Xₑ依存しているXᵢ
    • Xᵢ に空でない境界のセットがある
  • このような型変数が存在せず、まだ 未修正 型変数がある場合、型の推論は失敗します。
  • それ以外の場合、型変数 それ以上修正されていない場合 型の推論は成功します。
  • それ以外の場合は、対応するパラメーター型 EᵢTᵢを持つすべての引数に対して出力型 (§12.6.3.5) に未修正Xₑ変数が含まれますが、input 型 (§12.6.3.4 は含まれません)。 出力型推論 (§12.6.3.7) は、fromEᵢ toTᵢ に行われます。 その後、2 番目のフェーズが繰り返されます。

12.6.3.4 入力の種類

Eがメソッド グループまたは暗黙的に型指定された匿名関数で、Tがデリゲート型または式ツリー型の場合、Tのすべてのパラメーター型は input 型E with typeT

12.6.3.5 出力の種類

Eがメソッド グループまたは匿名関数で、Tがデリゲート型または式ツリー型の場合、Tの戻り値の型は output 型E with typeT

12.6.3.6 依存

固定されていない型変数Xᵢ Tᵥ Xₑを持つ引数Eᵥが発生した場合にXₑ固定されていない型変数 型がTᵥXᵢを持つEᵥinput型のは、Tᵥ型のEᵥ出力型で発生します。

Xₑdepends onXᵢXₑ に直接依存する場合XᵢまたはXᵢ に直接依存している場合XᵥXᵥ に依存しない場合Xₑ。 したがって、"depends on" は、"depends directly on" の推移的ですが、再帰的な閉鎖ではありません。

12.6.3.7 出力型の推論

出力型推論は、次のように型 T E されます。

  • Eが推論された戻り値の型U (§12.6.3.13) を持つ匿名関数であり、Tがデリゲート型または戻り値の型がTₓ式ツリー型の場合、 その後、lower バインド推論 (§12.6.3.10) がfromU toTₓ に作成されます。
  • それ以外の場合、Eがメソッド グループであり、Tがパラメーター型T₁...Tᵥおよび戻り値の型Tₓを持つデリゲート型または式ツリー型であり、戻り値の型T₁...TᵥEのオーバーロード解決により、戻り値の型がUされた単一のメソッドが生成された場合、lower バインド推論U からされますTₓ
  • それ以外の場合、 E が型 Uを持つ式である場合は、 lower バインド推論fromU toTになります。
  • それ以外の場合、推論は行われません。

12.6.3.8 明示的なパラメーター型の推論

explicit パラメーター型推論は、次のように型TE されます。

  • Eがパラメーター型U₁...Uᵥ明示的に型指定された匿名関数で、Tがパラメーター型がV₁...Vᵥデリゲート型または式ツリー型である場合、各Uᵢexact 推論 (§12.6.3.9) は、対応するVᵢUᵢ からされます

12.6.3.9 正確な推論

例の推論 fromU toVは次のように行われます。

  • Vunfixed の 1 つである場合XᵢUXᵢの正確な境界のセットに追加されます。
  • それ以外の場合、 V₁...VₑU₁...Uₑ のセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
    • V は配列型 V₁[...] で、 U は同じランクの配列型 U₁[...] です
    • V は型 V₁? で、 U は型です U₁
    • V は構築型 C<V₁...Vₑ> で、 U は構築された型です C<U₁...Uₑ>
      これらのケースのいずれかが該当する場合は、対応するVᵢに対する各Uᵢからの推定が行われます。
  • それ以外の場合、推論は行われません。

12.6.3.10 下限推論

U からにバインドされた推論Vは次のように行われます。

  • Vunfixed の 1 つである場合XᵢXᵢの一連の下限にUが追加されます。
  • それ以外の場合、 V が型 V₁? で、 U が型 U₁? 場合、 U₁ から V₁への下限推論が行われます。
  • それ以外の場合、 U₁...UₑV₁...Vₑ のセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
    • V は配列型 V₁[...]で、 U は同じランクの配列型 U₁[...]です
    • VIEnumerable<V₁>ICollection<V₁>IReadOnlyList<V₁>>IReadOnlyCollection<V₁> 、または IList<V₁> のいずれかであり、 U は 1 次元配列型です U₁[]
    • Vは構築されたclassstructinterface、またはdelegate型のC<V₁...Vₑ>であり、U (または、Uが型parameterの場合、その有効な基底クラスまたはその有効なインターフェイス セットのメンバー) が (直接または間接的に) 同一であるか inherits、(直接または間接的に) C<U₁...Uₑ>を実装する場合など、一意の型C<U₁...Uₑ>があります。
    • ("一意性" 制限は、インターフェイス C<T>{} class U: C<X>, C<Y>{}の場合、 U から C<T> への推論では、 U₁X または Y可能性があるため、推論は行われません。
      これらのケースのいずれかが適用される場合、次のように、各 Uᵢ から対応する Vᵢ への推論が行われます。
    • Uᵢが参照型であることが不明な場合は、exact 推論が行われます
    • それ以外の場合、Uが配列型の場合は、lower バインド推論が行われます
    • それ以外の場合、VC<V₁...Vₑ>場合、推論はCi-th型パラメーターに依存します。
      • 共変の場合は、 lower バインド推論 が行われます。
      • 反変の場合は、 upper バインド推論 が行われます。
      • インバリアントの場合は、 exact 推論 が行われます。
  • それ以外の場合、推論は行われません。

12.6.3.11 上限推論

U からVは次のように行われます。

  • Vunfixed の 1 つである場合XᵢUXᵢの上限のセットに追加されます。
  • それ以外の場合、 V₁...VₑU₁...Uₑ のセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
    • U は配列型 U₁[...]で、 V は同じランクの配列型 V₁[...]です
    • UIEnumerable<Uₑ>ICollection<Uₑ>IReadOnlyList<Uₑ>IReadOnlyCollection<Uₑ> 、または IList<Uₑ> のいずれかであり、 V は 1 次元配列型です Vₑ[]
    • U は型 U1? で、 V は型です V1?
    • U が構築されたクラス、構造体、インターフェイス、またはデリゲート型 C<U₁...Uₑ> であり、 Vclass, struct, interface 型または delegate 型であり、一意の型に identical 、直接または間接的に inherits 、または (直接的または間接的に) 一意の型を実装します C<V₁...Vₑ>
    • ("一意性" 制限は、インターフェイスが C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}されると、 C<U₁> から V<Q>への推論時に推論が行われないことを意味します。推論は、 U₁ から X<Q> または Y<Q>) に対して行われません。
      これらのケースのいずれかが適用される場合、次のように、各 Uᵢ から対応する Vᵢ への推論が行われます。
    • Uᵢが参照型であることが不明な場合は、exact 推論が行われます
    • それ以外の場合、Vが配列型の場合は、upper バインド推論が行われます
    • それ以外の場合、UC<U₁...Uₑ>場合、推論はCi-th型パラメーターに依存します。
      • 共変の場合は、 upper バインド推論 が行われます。
      • 反変の場合は、 より低い推論 が行われます。
      • インバリアントの場合は、 exact 推論 が行われます。
  • それ以外の場合、推論は行われません。

12.6.3.12 修正

一連の境界を持つ固定型変数Xᵢ次のように修正されます。

  • candidate 型のセットUₑは、Xᵢの境界セット内のすべての型のセットとして開始されます。
  • Xᵢの各バインドが順番に調べられ、Xᵢの正確なバインドされた U ごとに、Uと同じではないすべての型Uₑが候補セットから削除されます。 存在しないすべての型UₑXᵢの下限U ごとにUからの暗黙的な変換が候補セットから削除されます。 存在しないすべての型UₑXᵢ の各上限 U についてUへの暗黙的な変換が候補セットから削除されます。
  • 他のすべての候補型から暗黙的な変換がある一意の型VUₑ残りの候補の型の中に存在する場合、XᵢVに固定されます。
  • それ以外の場合、型の推論は失敗します。

12.6.3.13 推論された戻り値の型

匿名関数 F の推論された戻り値の型は、型の推論とオーバーロードの解決時に使用されます。 推論された戻り値の型は、すべてのパラメーター型が既知である匿名関数に対してのみ決定できます。これは、明示的に指定されているためか、匿名関数変換によって提供されるか、外側のジェネリック メソッド呼び出しで型推論中に推論されるためです。

推定された有効な戻り値の型は次のように決定されます。

  • Fの本体が型を持つの場合、Fの推定された有効な戻り値の型はその式の型です。
  • Fの本体がブロックでありブロックのreturnステートメント内の式のセットに最も一般的な型T (§12.6.3.15) がある場合、推定される有効な戻り値の型FT
  • それ以外の場合、有効な戻り値の型は、 Fに対して推論できません。

推論された戻り値の型は次のように決定されます。

  • Fが非同期で、Fの本体が nothing (§12.2) として分類された式であるか、returnステートメントに式がないブロックである場合、推論された戻り値の型は«TaskType»されます (§15.15.1)。
  • Fが非同期で、推定された有効な戻り値の型Tがある場合、推論される戻り値の型は «TaskType»<T>»(§15.15.1)。
  • Fが非非同期で、推定された有効な戻り値の型がT場合、推論された戻り値の型はT
  • それ以外の場合は、 Fの戻り値の型を推論できません。

: 匿名関数を含む型推論の例として、System.Linq.Enumerable クラスで宣言されたSelect拡張メソッドについて考えます。

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

System.Linq名前空間がusing namespace ディレクティブを使用してインポートされ、string型のName プロパティを持つクラス Customerが与えられた場合、Select メソッドを使用して顧客の一覧の名前を選択できます。

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Selectの拡張メソッド呼び出し (§12.8.10.3) は、呼び出しを静的メソッド呼び出しに書き換えることによって処理されます。

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

型引数は明示的に指定されていないため、型引数を推論するために型推論が使用されます。 まず、customers 引数はソース パラメーターに関連し、 TSourceCustomerと推論します。 次に、上記の匿名関数型推論プロセスを使用して、cCustomerが与えられ、式c.Nameはセレクター パラメーターの戻り値の型に関連し、stringTResult推論します。 したがって、呼び出しは

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

結果は IEnumerable<string>型です。

次の例は、匿名関数型の推論で、ジェネリック メソッド呼び出しの引数間で型情報を "フロー" できるようにする方法を示しています。 次のメソッドと呼び出しを指定します。

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

呼び出しの型推論は次のように続行されます。まず、引数 "1:15:30" は value パラメーターに関連し、 X を文字列として推論します。 次に、最初の匿名関数のパラメーターs、推論された型stringが与えられ、式TimeSpan.Parse(s)f1の戻り値の型に関連し、System.TimeSpanY推論されます。 最後に、2 番目の匿名関数 (t) のパラメーターには推論された型System.TimeSpanが与えられ、式t.TotalHoursf2の戻り値の型に関連し、doubleZ推論されます。 したがって、呼び出しの結果は double型になります。

end の例

12.6.3.14 メソッド グループの変換のための型推論

ジェネリック メソッドの呼び出しと同様に、ジェネリック メソッドを含むメソッド グループ M が特定のデリゲート型 D に変換されるときにも、型推論が適用されます (§10.8)。 メソッドを指定する

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

型推論のタスクDデリゲート型に割り当てられているメソッド グループMは、式が次のようにS₁...Sᵥ型引数を検索することです。

M<S₁...Sᵥ>

は、Dと互換性 (§20.2) になります。

ジェネリック メソッド呼び出しの型推論アルゴリズムとは異なり、この場合、引数 types、引数 式はありません。 特に、匿名関数がないため、推論の複数のフェーズは必要ありません。

代わりに、すべてのXᵢunfixed と見なされ、lower バインド推論D to の各引数型UₑMの対応するパラメーター型Tₑされます。 いずれかの Xᵢ で境界が見つからなかった場合、型の推論は失敗します。 それ以外の場合、すべてのXᵢ型推論の結果である対応するSᵢに修正されます。

12.6.3.15 一連の式の最も一般的な型を見つける

場合によっては、一連の式について共通の型を推論する必要があります。 特に、暗黙的に型指定された配列の要素型と、 block 本体を持つ匿名関数の戻り値の型は、この方法で見つかります。

E₁...Eᵥ一連の式に最も一般的な型は、次のように決定されます。

  • 新しい 修正されていない 型変数 X が導入されました。
  • 各式Ei出力型推論 (§12.6.3.7) が実行されてXされます。
  • X修正 (§12.6.3.12) であり、結果の型は最も一般的な型です。
  • それ以外の場合、推論は失敗します。

: 直感的にこの推論は、Eᵢを引数としてvoid M<X>(X x₁ ... X xᵥ)メソッドを呼び出し、Xを推論することと同じです。 end note

12.6.4 オーバーロードの解決

12.6.4.1 全般

オーバーロード解決は、引数リストと候補関数メンバーのセットを指定して呼び出す最適な関数メンバーを選択するためのバインディング時メカニズムです。 オーバーロード解決は、C# 内の次の個別のコンテキストで呼び出す関数メンバーを選択します。

  • invocation_expressionで名前が付けられたメソッドの呼び出し (§12.8.10)。
  • object_creation_expressionで名前が付けられたインスタンス コンストラクターの呼び出し (§12.8.17.2)。
  • element_accessを介したインデクサー アクセサーの呼び出し (§12.8.12)。
  • 式で参照される定義済み演算子またはユーザー定義演算子の呼び出し (§12.4.4 および §12.4.5)。

これらの各コンテキストは、候補関数メンバーのセットと、独自の方法で引数のリストを定義します。 たとえば、メソッド呼び出しの候補のセットには、オーバーライドとしてマークされたメソッド (§12.5) は含まれません。また、派生クラス内のメソッドが該当する場合 (§12.8.10.2) 基底クラスのメソッドは候補になりません。

候補関数メンバーと引数リストが特定されると、最適な関数メンバーの選択はすべてのケースで同じです。

次のサブクラウスは、適用可能な関数メンバー関数メンバーという用語の正確な意味を定義します。

12.6.4.2 該当する関数メンバー

関数メンバーは、次のすべてに該当する場合にA引数リストに関して適用可能な関数メンバーと呼びます。

  • Aの各引数は、§12.6.2.2 で説明されている関数メンバー宣言のパラメーターに対応し、最大 1 つの引数が各パラメーターに対応し、引数が対応しないパラメーターは省略可能なパラメーターです。
  • Aの各引数について、引数のパラメーター受け渡しモードは、対応するパラメーターのパラメーター受け渡しモードと同じです。
    • 値パラメーターまたはパラメーター配列の場合、引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在します。
    • 参照パラメーターまたは出力パラメーターの場合は、引数式の型 (存在する場合) と対応するパラメーターの型の間に ID 変換があります。
    • 対応する引数に in 修飾子がある場合、引数式の型 (存在する場合) と対応するパラメーターの型の間に ID 変換がある場合、または
    • 対応する引数が in 修飾子を省略した場合、入力パラメーターの場合、引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在します。

パラメーター配列を含む関数メンバーの場合、関数メンバーが上記の規則で適用できる場合は、 正規形式で適用できると言われます。 パラメーター配列を含む関数メンバーが通常の形式で適用できない場合、関数メンバーは代わりにその拡張形式で適用できます

  • 展開されたフォームは、関数メンバー宣言のパラメーター配列をパラメーター配列の要素型の 0 個以上の値パラメーターに置き換えて、引数リスト A の引数の数がパラメーターの合計数と一致します。 Aが関数メンバー宣言の固定パラメーターの数よりも少ない引数を持つ場合、関数メンバーの拡張形式を構築できないため、適用できません。
  • それ以外の場合、展開されたフォームは、 Aの各引数に適用されます。次のいずれかが当てはまります。
    • 引数のパラメーター受け渡しモードは、対応するパラメーターのパラメーター受け渡しモードと同じです。
      • 固定値パラメーターまたは拡張によって作成された値パラメーターの場合は、引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在します。
      • 参照渡しパラメーターの場合、引数式の型は、対応するパラメーターの型と同じです。
    • 引数のパラメーター受け渡しモードが value であり、対応するパラメーターのパラメーター受け渡しモードが入力され、引数式から対応するパラメーターの型への暗黙的な変換 (§10.2) が存在します

引数の型から入力パラメーターのパラメーター型への暗黙的な変換が動的暗黙的な変換 (§10.2.10) である場合、結果は未定義になります。

: 次の宣言とメソッド呼び出しが考えられます。

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

end の例

  • 静的メソッドは、メソッド グループが型を介して simple_name または member_access から発生する場合にのみ適用されます。
  • インスタンス メソッドは、メソッド グループが simple_name、変数または値を介した member_access 、または base_accessの結果である場合にのみ適用されます。
    • メソッド グループの結果が simple_nameの場合、インスタンス メソッドは、 this アクセスが許可されている場合にのみ適用 §12.8.14
  • §12.8.7.2 で説明されているように、メソッド グループがインスタンスまたは型のいずれかを介して実行できるmember_accessから得られる場合インスタンスメソッドと静的メソッドの両方が適用されます。
  • 型引数 (明示的に指定または推論) が制約を満たしていないジェネリック メソッドは適用できません。
  • メソッド グループ変換のコンテキストでは、メソッドの戻り値の型からデリゲートの戻り値の型への ID 変換 (§10.2.2) または暗黙的な参照変換 (§10.2.8) が存在する必要があります。 それ以外の場合、候補メソッドは適用されません。

12.6.4.3 Better 関数メンバー

より優れた関数メンバーを決定するために、削除された引数リスト A は、元の引数リストに表示される順序で引数式自体だけを含み、 out または ref 引数を除外して構築されます。

各候補関数メンバーのパラメーター リストは、次のように構築されます。

  • 展開フォームは、関数メンバーが展開フォームでのみ適用された場合に使用されます。
  • 対応する引数のない省略可能なパラメーターは、パラメーター リストから削除されます
  • パラメーター リストから参照パラメーターと出力パラメーターが削除される
  • パラメーターは、引数リスト内の対応する引数と同じ位置に配置されるように並べ替えられます。

引数リストA一連の引数式が{E₁, E₂, ..., Eᵥ}され、2 つの適用可能な関数メンバーがMᵥされ、パラメーター型が{P₁, P₂, ..., Pᵥ}および{Q₁, Q₂, ..., Qᵥ}Mₓされると、MᵥMₓよりもbetter 関数メンバーとして定義されます。

  • 引数ごとに、 Eᵥ から Qᵥ への暗黙的な変換は、 Eᵥ から Pᵥ への暗黙的な変換よりも優れたものではありません。
  • 少なくとも 1 つの引数の場合、 Eᵥ から Pᵥ への変換は、 Eᵥ から Qᵥ への変換よりも優れています。

パラメーターの型シーケンス {P₁, P₂, ..., Pᵥ}{Q₁, Q₂, ..., Qᵥ} が等しい場合 (つまり、各 Pᵢ は対応する Qᵢへの ID 変換を持ちます)、より適切な関数メンバーを決定するために、次のタイブレーク ルールが適用されます。

  • Mᵢが非ジェネリック メソッドであり、Mₑがジェネリック メソッドである場合、MᵢMₑよりも優れています。
  • それ以外の場合、Mᵢが通常の形式で適用され、Mₑに params 配列があり、展開された形式でのみ適用できる場合は、MₑよりもMᵢが適しています。
  • それ以外の場合、両方のメソッドがパラメーター配列を持ち、展開されたフォームでのみ適用可能であり、 Mᵢ の params 配列の要素が Mₑの params 配列よりも少ない場合、 MᵢMₑよりも優れています。
  • それ以外の場合、 MᵥMₓよりも具体的なパラメーター型がある場合、 MᵥMₓよりも優れています。 {R1, R2, ..., Rn}{S1, S2, ..., Sn}は、MᵥMₓの予期しないパラメーター型と、予期しないパラメーター型を表します。 Mᵥのパラメーター型は、各パラメーターに対して、RxSxよりも具体的でない場合に、Mₓよりも具体的であり、少なくとも 1 つのパラメーターに対して、RxSxよりも具体的である場合です。
    • 型パラメーターは、型以外のパラメーターよりも具体的ではありません。
    • 少なくとも 1 つの型引数がより具体的で、型引数が他の型引数の対応する型引数よりも具体的でない場合、構築された型は、(同じ数の型引数を持つ) 別の構築型よりも具体的です。
    • 配列型は、1 つ目の要素型が 2 番目の要素型よりも具体的な場合、(次元数が同じ) 別の配列型よりも具体的です。
  • それ以外の場合は、1 つのメンバーが非リフト演算子で、もう一方がリフトされた演算子である場合は、非リフト演算子の方が適しています。
  • どちらの関数メンバーも適切でなく、 Mᵥ のすべてのパラメーターに対応する引数があるのに対し、既定の引数は Mₓ の少なくとも 1 つの省略可能なパラメーターに置き換える必要がある場合、 MᵥMₓよりも優れています。
  • 少なくとも 1 つのパラメーターMᵥで、Mₓの対応するパラメーターよりも better パラメーター渡しの選択肢 (§12.6.4.4) を使用し、MₓのどのパラメーターもMᵥよりも適切なパラメーター渡しの選択肢を使用しない場合、MᵥMₓよりも優れています。
  • それ以外の場合は、関数メンバーの方が適していません。

12.6.4.4 パラメーター受け渡しモードの改善

次のように、2 つのパラメーターの 1 つに値渡しモードがある場合、2 つのオーバーロードされたメソッドで対応するパラメーターがパラメーター渡しモードによってのみ異なることがあります。

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

int i = 10;§12.6.4.2 によると、呼び出しM1(i)M1(i + 5)の両方のオーバーロードが適用されます。 このような場合、パラメーター受け渡しモードの値を持つメソッドは、 better パラメーターパッシング モードの選択肢です

: 入力、出力、または参照の受け渡しモードの引数は、まったく同じパラメーターの受け渡しモードにのみ一致するので、このような選択は必要ありません。 end note

12.6.4.5 式からの変換の改善

Eから型T₁に変換する暗黙的な変換C₁と、式Eから型T₂に変換する暗黙的な変換C₂を考えると、C₁より次のいずれかが保持されている場合C₂よりもです。

  • ET₁と完全に一致し、ET₂と完全に一致しません (§12.6.4.6)
  • ET₁T₂の両方または両方に完全に一致し、T₁T₂よりも優れた変換ターゲットです (§12.6.4.7)
  • Eはメソッド グループ (§12.2) であり、T₁は変換C₁のメソッド グループの単一の最適なメソッドと互換性があり (§20.4)、T₂は変換用のメソッド グループの単一の最適なメソッドと互換性がありませんC₂

12.6.4.6 厳密に一致する式

Eと型TE 、次のいずれかが保持されている場合は T完全に一致します。

  • E には型 Sがあり、id 変換は S から T
  • E は匿名関数です。 T はデリゲート型 D または式ツリー型 Expression<D> であり、次のいずれかが保持されます。
    • Dのパラメーター リスト (§12.6.3.12) のコンテキストにEの推定戻り値の型Xが存在し、Xから戻り値の型への ID 変換が存在しますD
    • E は戻り値のない async ラムダであり、 D には非ジェネリックである戻り値の型があります «TaskType»
    • Eが非非同期で、Dが戻り値の型Yを持つか、Eが非同期でD戻り値の型が «TaskType»<Y>(§15.15.1) であり、次のいずれかが保持されます。
      • Eの本体は、完全に一致する式です。Y
      • Eの本体は、すべての return ステートメントが完全に一致する式を返すブロックですY

12.6.4.7 コンバージョンターゲットの向上

T₁T₂の 2 つの型を指定すると、T₁は、次のいずれかが保持されている場合T₂よりもな変換ターゲットになります。

  • T₁からT₂への暗黙的な変換が存在し、T₂からT₁への暗黙的な変換は存在しません
  • T₁«TaskType»<S₁>(§15.15.1)、 T₂«TaskType»<S₂>S₁ は変換ターゲットよりも優れています S₂
  • T₁«TaskType»<S₁>(§15.15.1)、 T₂«TaskType»<S₂>であり、 T₁T₂
  • T₁S₁またはS₁?S₁が符号付き整数型で、T₂S₂またはS₂?S₂が符号なし整数型である場合です。 具体的には:
    • S₁sbyte であり、 S₂byteushortuint、または ulong
    • S₁short であり、 S₂ushortuint、または ulong
    • S₁int であり、 S₂uintされているか、 ulong
    • S₁long され、 S₂ulong

12.6.4.8 ジェネリック クラスでのオーバーロード

: 宣言されているシグネチャは一意である必要があります (§8.6)。型引数を置換すると、同じシグネチャになる可能性があります。 このような状況では、オーバーロードの解決では、元のシグネチャ (型引数の置換前) の最も具体的な (§12.6.4.3) が選択され、存在する場合はエラーが報告されます。 end note

: 次の例は、この規則に従って有効で無効なオーバーロードを示しています。

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

end の例

12.6.5 動的メンバー呼び出しのコンパイル時チェック

動的にバインドされた操作のオーバーロード解決は実行時に行われますが、コンパイル時にオーバーロードの選択元となる関数メンバーの一覧を把握できる場合があります。

  • デリゲート呼び出し (§12.8.10.4) の場合、リストは呼び出しの delegate_type と同じパラメーター リストを持つ 1 つの関数メンバーです
  • メソッドの呼び出し (§12.8.10.2) の場合、または静的な型が動的でない値の場合、メソッド グループ内のアクセス可能なメソッドのセットはコンパイル時に認識されます。
  • オブジェクト作成式 (§12.8.17.2) の場合、型内のアクセス可能なコンストラクターのセットはコンパイル時に認識されます。
  • インデクサー アクセス (§12.8.12.3) の場合、受信側のアクセス可能なインデクサーのセットはコンパイル時に認識されます。

このような場合は、既知の関数メンバーセット内の各メンバーに対して制限付きコンパイル時チェックが実行され、実行時に呼び出されることがないことがわかっているかどうかを確認します。 変更されたパラメーター F 関数メンバーごとに、引数リストが作成されます。

  • 最初に、 F がジェネリック メソッドであり、型引数が指定された場合は、パラメーター リスト内の型パラメーターに置き換えます。 ただし、型引数が指定されていない場合、このような置換は行われません。
  • 次に、型が開いているすべてのパラメーター (つまり、型パラメーターが含まれています。 §8.4.3 を参照) は、対応するパラメーターと共に省略されます。

Fが小切手に合格するためには、以下のすべてが保持されます。

  • Fの変更されたパラメーター リストは、§12.6.4.2 の観点から変更された引数リストに適用
  • 変更されたパラメーター リスト内のすべての構築された型は、制約を満たす (§8.4.5)。
  • 上記の手順で F の型パラメーターが置き換えられた場合、それらの制約は満たされます。
  • Fが静的メソッドの場合、メソッド グループは、コンパイル時にレシーバーが変数または値であることがわかっているmember_accessから発生したものではありません。
  • Fがインスタンス メソッドの場合、メソッド グループは、コンパイル時にレシーバーが型であることがわかっているmember_accessから発生したものではありません。

このテストに合格した候補がない場合は、コンパイル時エラーが発生します。

12.6.6 関数メンバーの呼び出し

12.6.6.1 全般

このサブクラウズでは、特定の関数メンバーを呼び出すために実行時に実行されるプロセスについて説明します。 バインディング時プロセスは、呼び出す特定のメンバーを既に決定していることを前提としています。場合によっては、一連の候補関数メンバーにオーバーロード解決を適用します。

呼び出しプロセスを記述するために、関数メンバーは次の 2 つのカテゴリに分類されます。

  • 静的関数メンバー。 これらは、静的メソッド、静的プロパティ アクセサー、およびユーザー定義演算子です。 静的関数メンバーは常に非仮想です。
  • インスタンス関数メンバー。 これらは、インスタンス メソッド、インスタンス コンストラクター、インスタンス プロパティ アクセサー、およびインデクサー アクセサーです。 インスタンス関数メンバーは非仮想または仮想であり、常に特定のインスタンスで呼び出されます。 インスタンスはインスタンス式によって計算され、 this として関数メンバー内でアクセスできるようになります (§12.8.14)。 インスタンス コンストラクターの場合、インスタンス式は新しく割り当てられたオブジェクトになります。

関数メンバー呼び出しの実行時処理は、次の手順で構成されます。ここで、 M は関数メンバーであり、 M がインスタンス メンバーの場合はインスタンス式 E

  • Mが静的関数メンバーの場合:

    • 引数リストは、 §12.6.2 で説明されているように評価されます。
    • M が呼び出されます。
  • それ以外の場合、 E の型が値型の Vであり、 MVで宣言またはオーバーライドされる場合:

    • E が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。 インスタンス コンストラクターの場合、この評価は、新しいオブジェクトにストレージ (通常は実行スタックから) を割り当てることで構成されます。 この場合、 E は変数として分類されます。
    • Eが変数として分類されていない場合、またはVが読み取り専用構造体型 (§16.2.2) でない場合、Eは次のいずれかです。
      • 入力パラメーター (§15.6.2.3.2) または
      • readonly フィールド (§15.5.3) または
      • readonly参照変数または戻り値 (§9.7)

    その後、 Eの型の一時ローカル変数が作成され、 E の値がその変数に割り当てられます。 E は、その一時ローカル変数への参照として再分類されます。 一時変数は、M内でthisとしてアクセスできますが、他の方法ではアクセスできません。 したがって、Eを書き込むことができる場合にのみ、呼び出し元がthisに加える変更M観察することができます。

    • 引数リストは、 §12.6.2 で説明されているように評価されます。
    • M が呼び出されます。 Eによって参照される変数は、thisによって参照される変数になります。
  • それ以外:

    • E が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
    • 引数リストは、 §12.6.2 で説明されているように評価されます。
    • Eの型がvalue_typeの場合、Eclass_typeに変換するためにボックス化変換 (§10.2.9) が実行され、Eは次の手順でそのclass_typeと見なされます。 value_typeenum_typeの場合、class_typeSystem.Enum;され、それ以外の場合はSystem.ValueType
    • Eの値が有効であることが確認されます。 Eの値が null の場合、System.NullReferenceExceptionがスローされ、それ以上の手順は実行されません。
    • 呼び出す関数メンバーの実装が決定されます。
      • Eのバインド時の型がインターフェイスの場合、呼び出す関数メンバーは、Eによって参照されるインスタンスのランタイム型によって提供されるMの実装です。 この関数メンバーは、インターフェイス マッピング 規則 (§18.6.5) を適用して、Eによって参照されるインスタンスのランタイム型によって提供されるMの実装を決定することによって決定されます。
      • それ以外の場合、Mが仮想関数メンバーの場合、呼び出す関数メンバーは、Eによって参照されるインスタンスのランタイム型によって提供されるMの実装です。 この関数メンバーは、Eによって参照されるインスタンスのランタイム型に関して、Mの最も派生した実装 (§15.6.4) を決定するための規則を適用することによって決定されます。
      • それ以外の場合、 M は非仮想関数メンバーであり、呼び出す関数メンバーはそれ自体 M
    • 上記の手順で決定した関数メンバーの実装が呼び出されます。 Eによって参照されるオブジェクトは、このオブジェクトによって参照されるオブジェクトになります。

インスタンス コンストラクター (§12.8.17.2) の呼び出しの結果は、作成された値です。 他の関数メンバーの呼び出しの結果は、その本体から返された値 (存在する場合) です (§13.10.5)。

12.6.6.2 ボックス化されたインスタンスでの呼び出し

value_typeに実装されている関数メンバーは、次の状況で、そのvalue_typeのボックス化されたインスタンスを介して呼び出すことができます。

  • 関数メンバーが型 class_type から継承されたメソッドのオーバーライドであり、その class_typeのインスタンス式を介して呼び出される場合。

    : class_type は常に、 System.ObjectSystem.ValueType 、または System.Enumのいずれかになります。 end note

  • 関数メンバーがインターフェイス関数メンバーの実装であり、 interface_typeのインスタンス式を介して呼び出される場合。
  • デリゲートを介して関数メンバーが呼び出されたとき。

このような状況では、ボックス化されたインスタンスには value_typeの変数が含まれていると見なされ、この変数は関数メンバー呼び出し内でこの変数によって参照される変数になります。

: 特に、これは、ボックス化されたインスタンスで関数メンバーが呼び出されたときに、関数メンバーがボックス化されたインスタンスに含まれる値を変更できる可能性があることを意味します。 end note

12.7 分解

分解とは、式が個々の式のタプルに変換されるプロセスです。 分解は、単純な代入のターゲットがタプル式である場合に、そのタプルの各要素に割り当てる値を取得するために使用されます。

Eは、次のようにn要素を持つタプル式に分解されます。

  • En要素を持つタプル式の場合、分解の結果は式自体E
  • それ以外の場合、En要素を持つタプル型(T1, ..., Tn)がある場合、Eは一時変数__vに評価され、分解の結果は式(__v.Item1, ..., __v.Itemn)になります。
  • それ以外の場合、式 E.Deconstruct(out var __v1, ..., out var __vn) コンパイル時に一意のインスタンスまたは拡張メソッドに解決された場合、その式が評価され、分解の結果は式 (__v1, ..., __vn)。 このようなメソッドは、 deconstructor と呼ばれます。
  • それ以外の場合、 E を分解することはできません。

ここでは、 __v__v1, ..., __vn 、それ以外の場合は非表示でアクセスできない一時変数を参照します。

: dynamic 型の式を分解することはできません。 end note

12.8 主な式

12.8.1 全般

主な式には、最も単純な形式の式が含まれます。

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

: これらの文法規則は、ANTLR が処理しない一連の相互左再帰規則 (primary_expressionprimary_no_array_creation_expressionmember_accessinvocation_expressionelement_accesspost_increment_expressionpost_decrement_expressionnull_forgiving_expressionpointer_member_accesspointer_element_access) の一部であるため、ANTLR 対応ではありません。 標準の手法を使用して文法を変換し、相互左再帰を削除できます。 これは、すべての解析戦略がそれを必要とするわけではないため(LALRパーサーではそうはいかない)、これを行うと構造と説明が難読化されるわけではありません。 end note

pointer_member_access (§23.6.3) と pointer_element_access (§23.6.4) は、安全でないコード (§23) でのみ使用できます。

プライマリ式は、 array_creation_expressionprimary_no_array_creation_expressionの間で分割されます。 array_creation_expressionを他の単純な式形式と共に一覧表示するのではなく、このように扱うことで、文法で混乱を招く可能性のあるコード (例:

object o = new int[3][1];

それ以外の場合は次のように解釈されます。

object o = (new int[3])[1];

12.8.2 リテラル

literal (§6.4.5) で構成されるprimary_expressionは、値として分類されます。

12.8.3 挿入文字列式

interpolated_string_expressionは、$$@、または@$で構成され、その直後に"文字内のテキストが続きます。 引用符で囲まれたテキスト内には、0 個以上のinterpolations{および}文字で区切られ、それぞれがおよび省略可能な書式指定を囲みます。

挿入文字列式には 2 つの形式があります。regular (interpolated_regular_string_expression) と verbatim (interpolated_verbatim_string_expression)。構文的には似ていますが、意味的には異なる 2 つの形式の文字列リテラル (§6.4.5.6)。

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

上記で定義した 6 つの構文規則は、次のようにコンテキストに依存します。

Rule コンテキスト要件
Interpolated_Regular_String_Mid Interpolated_Regular_String_Start後、次の補間の間、および対応するInterpolated_Regular_String_Endの前にのみ認識されます。
Regular_Interpolation_Format regular_interpolation内でのみ認識され、開始コロン (:)が任意の種類の角かっこ (かっこ/中かっこ/正方形) 内に入れ子になっていない場合。
Interpolated_Regular_String_End Interpolated_Regular_String_Startの後でのみ認識され、介入するトークンがInterpolated_Regular_String_Midまたはregular_interpolationの一部となり得るトークン (そのような補間に含まれるinterpolated_regular_string_expressionのトークンを含む) である場合にのみ認識されます。
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End これら 3 つの規則の認識は、上記の対応する規則に従い、 規則 対応する verbatim 1 に置き換えられました。

: 上記の規則は、定義が言語内の他のトークンの定義と重複する場合、コンテキストに依存します。 end note

: 上記の文法は、コンテキストに依存する字句規則のため、ANTLR 対応ではありません。 他の lexer ジェネレーターと同様に、ANTLR はコンテキストに依存する字句規則をサポートします。たとえば、 非現実的なモードを使用しますがこれは実装の詳細であるため、この仕様の一部ではありません。 end note

interpolated_string_expressionは値として分類されます。 暗黙的な挿入文字列変換 (§10.2.5) を使用してSystem.IFormattableまたはSystem.FormattableStringにすぐに変換される場合、挿入文字列式にはその型があります。 それ以外の場合は、型が string

: interpolated_string_expression で使用できる型の違いは、 System.String (§C.2) と System.FormattableString (§C.3) のドキュメントから決定できます。 end note

補間の意味は、regular_interpolationverbatim_interpolationの両方で、式の値をRegular_Interpolation_FormatまたはVerbatim_Interpolation_Formatで指定された形式に従って、または式の型の既定の形式に従ってstringとして書式設定することです。 書式設定された文字列は、interpolation_minimum_width (存在する場合) によって変更され、interpolated_string_expressionに補間される最終的なstringが生成されます。

: 型の既定の形式の決定方法については、 System.String (§C.2) と System.FormattableString (§C.3) のドキュメントで詳しく説明されています。 Regular_Interpolation_FormatVerbatim_Interpolation_Formatで同じ標準形式の説明は、System.IFormattable (§C.4) のドキュメントと、標準ライブラリ (§C) の他の型で確認できます。 end note

interpolation_minimum_widthでは、constant_expressionintへの暗黙的な変換を持つ必要があります。 フィールドの幅このconstant_expressionの絶対値にし、アライメントこのconstant_expressionの値の符号 (正または負) にします。

  • フィールド幅の値が書式設定された文字列の長さ以下の場合、書式設定された文字列は変更されません。
  • それ以外の場合、書式設定された文字列の長さがフィールドの幅と等しくなるように、空白文字が埋め込まれます。
    • 配置が正の場合、書式設定された文字列はパディングの前に置くことで右揃えになります。
    • それ以外の場合は、パディングを追加して左揃えになります。

上記の書式設定や補間の埋め込みを含む、interpolated_string_expressionの全体的な意味 は、式からメソッド呼び出しへの変換によって定義されます。式の型がSystem.IFormattableまたはSystem.FormattableString場合、そのメソッドはSystem.FormattableString型の値を返すSystem.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) です。それ以外の場合は、型がstringされ、メソッドはstring型の値を返すstring.Format (§C.2) になります。

どちらの場合も、呼び出しの引数リストは、形式文字列リテラル書式指定補間ごとに、および書式指定に対応する各式の引数で構成されます。

書式指定文字列リテラルは次のように構築されます。ここで、 Ninterpolated_string_expression内の補間の数です。 書式指定文字列リテラルは、次の順序で構成されます。

  • Interpolated_Regular_String_StartまたはInterpolated_Verbatim_String_Startの文字
  • Interpolated_Regular_String_MidまたはInterpolated_Verbatim_String_Midの文字 (存在する場合)
  • 次に、0からN-1I各数値にN ≥ 1する場合:
    • プレースホルダーの仕様:
      • 左中かっこ ({) 文字
      • の 10 進表現 I
      • 次に、対応する regular_interpolation または verbatim_interpolationinterpolation_minimum_widthがある場合は、コンマ (,) の後に constant_expressionの値の 10 進表現が続きます。
      • 対応するregular_interpolationまたはverbatim_interpolationのRegular_Interpolation_FormatまたはVerbatim_Interpolation_Formatの文字 (存在する場合)
      • 右中かっこ (}) 文字
    • 対応する補間の直後に Interpolated_Regular_String_Mid または Interpolated_Verbatim_String_Mid の文字 (存在する場合)
  • 最後に、 Interpolated_Regular_String_End または Interpolated_Verbatim_String_Endの文字。

後続の引数は、補間からの です (存在する場合)。

interpolated_string_expressionに複数の補間が含まれている場合、それらの補間の式は左から右のテキスト順で評価されます。

例:

この例では、次の書式指定機能を使用します。

  • 整数を大文字の 16 進数として書式設定する X 書式指定。
  • string値の既定の形式は値自体です。
  • 指定された最小フィールド幅内で右揃えする正の配置値
  • 指定された最小フィールド幅内で左揃えの負の配置値、
  • interpolation_minimum_widthの定義済み定数、および
  • {{}}は、それぞれ{}として書式設定されます。

たとえば、以下のように指定したとします。

string text = "red";
int number = 14;
const int width = -4;

その後、以下を実行します。

挿入文字列式 同等の意味 string Value
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

end の例

12.8.4 単純な名前

simple_nameは識別子で構成され、必要に応じて型引数リストが続きます。

simple_name
    : identifier type_argument_list?
    ;

simple_nameはフォーム Iまたはフォーム I<A₁, ..., Aₑ>のいずれかです。ここで、Iは 1 つの識別子で、I<A₁, ..., Aₑ>は省略可能なtype_argument_listです。 type_argument_listが指定されていない場合は、eを 0 にすることを検討してください。 simple_nameは次のように評価され、分類されます。

  • eが 0 で、simple_name ローカル変数宣言空間 (§7.3) 内にローカル変数、パラメーター、または名前Iの定数が直接含まれている場合、simple_nameはそのローカル変数、パラメーター、または定数を参照し、変数または値として分類されます。
  • eが 0 で、simple_nameがジェネリック メソッド宣言内でattributesmethod_declarationの外部に表示され、その宣言に名前Iを持つ型パラメーターが含まれている場合、simple_nameはその型パラメーターを参照します。
  • それ以外の場合は、各インスタンス型 T (§15.3.2) に対して、すぐに外側の型宣言のインスタンス型から始まり、外側にある各クラスまたは構造体宣言のインスタンス型 (存在する場合) を続行します。
    • eが 0 で、Tの宣言に名前Iを持つ型パラメーターが含まれている場合、simple_nameはその型パラメーターを参照します。
    • それ以外の場合、e型引数を持つT内のIのメンバー参照 (§12.5) が一致を生成する場合:
      • Tが、すぐに囲むクラスまたは構造体の型のインスタンス型であり、ルックアップによって 1 つ以上のメソッドが識別される場合、結果は、thisのインスタンス式が関連付けられたメソッド グループになります。 型引数リストが指定されている場合は、ジェネリック メソッド (§12.8.10.2) の呼び出しで使用されます。
      • それ以外の場合、Tがすぐに外側のクラスまたは構造体型のインスタンス型である場合は、 参照によってインスタンス メンバーが識別され、インスタンス コンストラクター、インスタンス メソッド、またはインスタンス アクセサー (§12.2.1block内で参照が発生した場合、結果はフォーム this.Iのメンバー アクセス (§12.8.7) と同じです。 これは、 e が 0 の場合にのみ発生します。
      • それ以外の場合、結果はフォーム T.IまたはT.I<A₁, ..., Aₑ>のメンバー アクセス (§12.8.7) と同じです。
  • それ以外の場合は、simple_nameが発生する名前空間から始まり、外側の各名前空間 (存在する場合) を続行し、グローバル名前空間で終わる名前空間 Nごとに、エンティティが配置されるまで、次の手順が評価されます。
    • eが 0 で、IN 内の名前空間の名前である場合は、次のようになります。
      • simple_nameが発生する場所がNの名前空間宣言で囲まれており、名前空間宣言に名前Iを名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、simple_nameはあいまいになり、コンパイル時エラーが発生します。
      • それ以外の場合、simple_nameNI という名前の名前空間を参照します。
    • それ以外の場合、 N に名前 Ie 型パラメーターを持つアクセス可能な型が含まれている場合は、次のようになります。
      • eが 0 で、simple_nameが発生する場所がNの名前空間宣言で囲まれており、名前空間宣言に名前Iを名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、simple_nameはあいまいになり、コンパイル時エラーが発生します。
      • それ以外の場合、 namespace_or_type_name は、指定された型引数で構築された型を参照します。
    • それ以外の場合、 simple_name が発生する場所が、 Nの名前空間宣言で囲まれている場合:
      • eが 0 で、名前Iをインポートされた名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが名前空間宣言に含まれている場合、simple_nameはその名前空間または型を参照します。
      • それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前 Ie 型パラメーターを持つ型が 1 つだけ含まれている場合、 simple_name は、指定された型引数で構築されたその型を参照します。
      • それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前 I および e 型パラメーターを持つ複数の型が含まれている場合、 simple_name はあいまいになり、コンパイル時エラーが発生します。

    : このステップ全体は、 namespace_or_type_name (§7.8) の処理における対応するステップとまったく同じになります。 end note

  • それ以外の場合、 e が 0 で、 I が識別子 _の場合、 simple_namesimple discard です。これは宣言式の形式です (§12.17)。
  • それ以外の場合、 simple_name は未定義であり、コンパイル時エラーが発生します。

12.8.5 かっこで抽出された式

parenthesized_expressionは、かっこで囲まれたで構成されます。

parenthesized_expression
    : '(' expression ')'
    ;

parenthesized_expressionは、かっこ内のを評価することによって評価されます。 かっこ内の が名前空間または型を示している場合は、コンパイル時エラーが発生します。 それ以外の場合、 parenthesized_expression の結果は、含まれる 式の評価の結果

12.8.6 タプル式

tuple_expressionはタプルを表し、2 つ以上のコンマ区切りで構成され、必要に応じて名前が付けられた expressionかっこで囲まれています。 deconstruction_expressionは、暗黙的に型指定された宣言式を含むタプルの短縮構文です。

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

tuple_expressionはタプルとして分類されます。

deconstruction_expression var (e1, ..., en)tuple_expression (var e1, ..., var en)の短縮形であり、同じ動作に従います。 これは、deconstruction_expression内の入れ子になったdeconstruction_tupleに再帰的に適用されます。 したがって、 deconstruction_expression 内に入れ子になった各識別子は、宣言式 (§12.17) を導入します。 その結果、 deconstruction_expression は単純な割り当ての左側でのみ発生します。

タプル式は、各要素式が型Tiを持つ場合にのみ、Ei型を持っています。 型はタプル式と同じアリティのタプル型で、各要素は次のように指定されます。

  • 対応する位置のタプル要素に名前 Niがある場合、タプル型要素は Ti Ni
  • それ以外の場合、EiNiE.Ni、またはE?.Niの形式の場合、タプル型要素はTi Ni次のいずれかを保持します。
    • タプル式の別の要素に Niという名前があります。
    • 名前のない別のタプル要素には、フォーム Ni または E.Ni または E?.Niのタプル要素式があります。
    • NiItemX形式です。ここで、 X はタプル要素の位置を表す0開始されない 10 進数のシーケンスであり、 X は要素の位置を表しません。
  • それ以外の場合、タプル型要素は Ti

タプル式は、各要素式を左から右に順番に評価することによって評価されます。

タプル値は、タプル式からタプル型 (§10.2.13) に変換するか、値 (§12.2.2) として再分類するか、分解割り当てのターゲット (§12.21.2) にすることで取得できます。

例:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

この例では、4 つのタプル式すべてが有効です。 最初の 2 つ ( t1t2) はタプル式の型を使用せず、代わりに暗黙的なタプル変換を適用します。 t2の場合、暗黙的なタプル変換は、2からlongへの暗黙的な変換、およびnullからstringへの暗黙的な変換に依存します。 3 番目のタプル式には (int i, string)型があるため、その型の値として再分類できます。 一方、 t4の宣言はエラーです。タプル式には型がないため、2 番目の要素には型がありません。

if ((x, y).Equals((1, 2))) { ... };

この例は、タプル式がメソッド呼び出しの唯一の引数である場合に、タプルが複数のかっこのレイヤーにつながる場合があることを示しています。

end の例

12.8.7 メンバー アクセス

12.8.7.1 全般

member_accessは、primary_expressionpredefined_type、またはqualified_alias_memberで構成され、その後に "." トークンが続き、その後に identifier、必要に応じてtype_argument_listが続きます。

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

qualified_alias_member運用は、§14.8 で定義されています。

member_accessはフォーム E.Iまたはフォーム E.I<A₁, ..., Aₑ>のいずれかです。ここで、Eprimary_expressionpredefined_type、またはqualified_alias_member、 Iは 1 つの識別子であり、<A₁, ..., Aₑ>は省略可能なtype_argument_listです。 type_argument_listが指定されていない場合は、eを 0 にすることを検討してください。

dynamic型のprimary_expressionを持つmember_accessは動的にバインドされます (§12.3.3)。 この場合、コンパイラはメンバー アクセスを型 dynamicのプロパティ アクセスとして分類します。 primary_expressionのコンパイル時の型ではなく実行時の型を使用して、実行時にmember_accessの意味を判断するための以下の規則が適用されます。 この実行時分類がメソッド グループにつながる場合、メンバー アクセスはinvocation_expressionprimary_expressionになります。

member_accessは次のように評価され、分類されます。

  • eがゼロで、Eが名前空間であり、Eに名前がI入れ子になった名前空間が含まれている場合、結果はその名前空間になります。
  • それ以外の場合、 E が名前空間であり、 E に名前 I および K 型パラメーターを持つアクセス可能な型が含まれている場合、その型は指定された型引数で構築されます。
  • Eが型として分類されている場合、Eが型パラメーターでない場合、およびK型パラメーターを持つEIのメンバー参照 (§12.5) が一致を生成した場合、E.Iは評価され、次のように分類されます。

    : このようなメンバー参照の結果がメソッド グループで、 K が 0 の場合、メソッド グループには型パラメーターを持つメソッドを含めることができます。 これにより、このようなメソッドを型引数の推論と見なすことができます。 end note

    • Iが型を識別する場合、結果は、指定された型引数を使用して構築された型になります。
    • Iが 1 つ以上のメソッドを識別する場合、結果はインスタンス式が関連付けられていないメソッド グループになります。
    • Iが静的プロパティを識別する場合、結果は、関連付けられたインスタンス式のないプロパティ アクセスになります。
    • Iが静的フィールドを識別する場合:
      • フィールドが読み取り専用で、そのフィールドが宣言されているクラスまたは構造体の静的コンストラクターの外部で参照が発生した場合、結果は値、つまりEI静的フィールドの値になります。
      • それ以外の場合、結果は変数、つまりEI静的フィールドです。
    • Iが静的イベントを識別する場合:
      • イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言された場合 (§15.8.1)、 E.I は静的フィールドであるかのように正確に処理 I
      • それ以外の場合、結果は、インスタンス式が関連付けられていないイベント アクセスになります。
    • Iが定数を識別する場合、結果は値、つまりその定数の値になります。
    • Iが列挙メンバーを識別する場合、結果は値 (つまり、その列挙メンバーの値) になります。
    • それ以外の場合、 E.I は無効なメンバー参照であり、コンパイル時エラーが発生します。
  • Eがプロパティ アクセス、インデクサー アクセス、変数、または値である場合、その型がTされ、K型引数を持つTIのメンバー参照 (§12.5) によって一致が生成され、E.Iが評価され、次のように分類されます。
    • 最初に、 E がプロパティまたはインデクサー アクセスの場合、プロパティまたはインデクサー アクセスの値が取得され (§12.2.2)、E が値として再分類されます。
    • Iが 1 つ以上のメソッドを識別する場合、結果は、Eのインスタンス式が関連付けられたメソッド グループになります。
    • Iがインスタンス プロパティを識別する場合、結果は、Eの関連付けられたインスタンス式と、プロパティの型である関連付けられた型を持つプロパティ アクセスになります。 Tがクラス型の場合、関連付けられている型は、Tで始まり、その基底クラスを検索するときに見つかったプロパティの最初の宣言またはオーバーライドから選択されます。
    • Tclass_typeで、Iがそのclass_typeのインスタンス フィールドを識別する場合:
      • Eの値がnull場合は、System.NullReferenceExceptionがスローされます。
      • それ以外の場合、フィールドが読み取り専用で、フィールドが宣言されているクラスのインスタンス コンストラクターの外部で参照が発生した場合、結果は値、つまり、Eによって参照されるオブジェクト内のフィールドIの値になります。
      • それ以外の場合、結果は変数になります。つまり、Eによって参照されるオブジェクト内のフィールドI
    • Tstruct_typeで、Iがそのstruct_typeのインスタンス フィールドを識別する場合:
      • Eが値である場合、またはフィールドが読み取り専用で、フィールドが宣言されている構造体のインスタンス コンストラクターの外部で参照が発生した場合、結果は値、つまり、Eによって指定された構造体インスタンス内のフィールド Iの値になります。
      • それ以外の場合、結果は変数になります。つまり、Eによって指定された構造体インスタンス内のフィールドI
    • Iがインスタンス イベントを識別する場合:
      • イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言され (§15.8.1)、参照が a += 演算子または -= 演算子の左側として発生しない場合、 E.I はインスタンス フィールド I とまったく同じように処理されます。
      • それ以外の場合、結果は、 Eの関連するインスタンス式を使用したイベント アクセスになります。
  • それ以外の場合は、拡張メソッドの呼び出しとして E.I を処理しようとしました (§12.8.10.3)。 これが失敗した場合、 E.I は無効なメンバー参照であり、バインド時エラーが発生します。

12.8.7.2 同一の単純名と型名

フォーム E.Iのメンバー アクセスで、 Eが単一の識別子であり、simple_nameとしてのEの意味 (§12.8.4) が定数、フィールド、プロパティ、ローカル変数、またはtype_nameとしてのEの意味と同じ型 (§7.8.1) である場合は、Eの両方の意味が許可されます。 E.Iのメンバー参照はあいまいになることはありません。どちらの場合も、Iは必ず型Eのメンバーである必要があるためです。 言い換えると、この規則は、コンパイル時エラーが発生した静的メンバーと入れ子になった型の E へのアクセスを許可するだけです。

例:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

公開目的でのみ、A クラス内では、Color型を参照するColor識別子が«...»で区切られ、Color フィールドを参照する識別子は区切りません。

end の例

12.8.8 Null 条件付きメンバー アクセス

null_conditional_member_accessmember_accessの条件付きバージョン (§12.8.7) であり、結果の種類がvoid場合はバインド時エラーです。 結果の型が void 可能性がある null 条件式については、(§12.8.11 を参照してください)。

null_conditional_member_accessは、primary_expressionの後に 2 つのトークン "?" と "." が続き、その後に省略可能なtype_argument_listを持つ identifier、その後に 0 個以上のdependent_accessが続き、いずれも null_forgiving_operator で割り当てることができる 1 つ以上のdependent_accessで構成されます。

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

null_conditional_member_accessEは、P?.A形式です。 Eの意味は次のように決定されます。

  • Pの型が null 許容値型の場合:

    TP.Value.Aの種類にしましょう。

    • Tが参照型または null 非許容値型のいずれでもない型パラメーターである場合、コンパイル時エラーが発生します。

    • Tが null 非許容値型の場合、Eの型はT?され、Eの意味は次の意味と同じです。

      ((object)P == null) ? (T?)null : P.Value.A
      

      ただし、 P は 1 回だけ評価されます。

    • それ以外の場合、 E の型は Tされ、 E の意味は次の意味と同じです。

      ((object)P == null) ? (T)null : P.Value.A
      

      ただし、 P は 1 回だけ評価されます。

  • それ以外:

    TP.Aの型にしましょう。

    • Tが参照型または null 非許容値型のいずれでもない型パラメーターである場合、コンパイル時エラーが発生します。

    • Tが null 非許容値型の場合、Eの型はT?され、Eの意味は次の意味と同じです。

      ((object)P == null) ? (T?)null : P.A
      

      ただし、 P は 1 回だけ評価されます。

    • それ以外の場合、 E の型は Tされ、 E の意味は次の意味と同じです。

      ((object)P == null) ? (T)null : P.A
      

      ただし、 P は 1 回だけ評価されます。

: 次の形式の式を使用します。

P?.A₀?.A₁

Pが評価null場合、A₀A₁も評価されません。 式が null_conditional_member_access または null_conditional_element_access §12.8.13 操作のシーケンスである場合も同様です。

end note

null_conditional_projection_initializernull_conditional_member_accessの制限であり、同じセマンティクスを持っています。 匿名オブジェクト作成式 (§12.8.17.7) のプロジェクション初期化子としてのみ発生します。

12.8.9 Null を許容する式

12.8.9.1 全般

null を許容する式の値、型、分類 (§12.2) とセーフ コンテキスト (§16.4.12) は、その primary_expressionの値、型、分類、およびセーフ コンテキストです。

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

: 後置の null 許容演算子とプレフィックス論理否定演算子 (§12.9.4) は、同じ字句トークン (!) で表されますが、異なります。 オーバーライドできるのは後者 (§15.10) だけです。null 許容演算子の定義は修正されます。 end note

null を許容する演算子を同じ式に複数回適用すると、コンパイル時エラーになります。ただし、かっこの間に入ります。

: 次はすべて無効です。

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

end の例

このサブクラウスの残りの部分と次の兄弟サブクラウゼは、条件付きで規範的です。

静的 null 状態分析 (§8.9.5) を実行するコンパイラは、次の仕様に準拠する必要があります。

null 許容演算子は、コンパイル時の擬似演算であり、コンパイラの静的 null 状態分析を通知するために使用されます。 これには、式が null 値 許容されるというコンパイラの判断をオーバーライドし;null 許容に関連する警告を発行するコンパイラをオーバーライドするという 2 つの用途があります。

コンパイラの静的 null 状態分析で警告が生成されない式に null 許容演算子を適用してもエラーではありません。

12.8.9.2 "null" の判定をオーバーライドする

状況によっては、コンパイラの静的 null 状態分析によって、式の null 状態が null maybe null であると判断され 他の情報が式を null にできないことが示された場合に診断警告を発行することがあります。 このような式に null を許容する演算子を適用すると、コンパイラの静的な null 状態分析に、null 状態が null でないことが通知されます;これにより、診断警告が回避され、進行中の分析が通知される可能性があります。

: 次の点を考慮してください。

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

IsValidtrueを返す場合、pを安全に逆参照してNameプロパティにアクセスできます。また、!を使用して "null 値の逆参照" 警告を抑制できます。

end の例

例: null を許容する演算子は慎重に使用する必要があります。次の点を考慮してください。

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

ここでは、null を許容する演算子が値型に適用され、 xの警告が破棄されます。 ただし、実行時に xnull された場合、 nullintにキャストできないため、例外がスローされます。

end の例

12.8.9.3 他の null 分析警告のオーバーライド

maybe null をオーバーライドすることに加えて上記のような決定は、式に 1 つ以上の警告が必要であるというコンパイラの静的 null 状態分析の決定をオーバーライドすることが必要なその他の状況がある場合があります。 このような式要求に null を許容する演算子を適用すると、コンパイラは式に対して警告を発行しません。 これに対して、コンパイラは警告を発行しないことを選択し、さらに分析を変更することもできます。

: 次の点を考慮してください。

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

メソッド Assignのパラメーターの型 ( lv & rv) は string?され、 lv は出力パラメーターであり、単純な割り当てが実行されます。

メソッドMAssignの出力パラメーターとして、string型の変数sを渡します。sは null 許容変数ではないため、コンパイラによって警告が発行されます。 Assignの 2 番目の引数を null にできないことを考えると、null を許容する演算子を使用して警告を破棄します。

end の例

条件付きで規範的なテキストの末尾。

12.8.10 呼び出し式

12.8.10.1 全般

invocation_expressionは、メソッドを呼び出すために使用されます。

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

primary_expressionは、delegate_typeがある場合にのみ、null_forgiving_expressionになる可能性があります。

次の少なくとも 1 つが保持されている場合、 invocation_expression は動的にバインドされます (§12.3.3)。

  • primary_expressionにはコンパイル時の型dynamicがあります。
  • 省略可能な argument_list の少なくとも 1 つの引数にコンパイル時の型が dynamic

この場合、コンパイラは invocation_expressiondynamic型の値として分類します。 invocation_expressionの意味を判断するための以下の規則は、コンパイル時の型の代わりに実行時の型を使用して実行時に適用され、primary_expressionの型とコンパイル時の型がdynamic引数の代わりに適用されます。 primary_expressionにコンパイル時の型dynamicがない場合、メソッドの呼び出しでは、§12.6.5 で説明されているように、コンパイル時のチェックが制限されます。

invocation_expressionprimary_expressionは、メソッド グループまたはdelegate_typeの値である必要があります。 primary_expressionがメソッド グループの場合、invocation_expressionはメソッド呼び出しです (§12.8.10.2)。 primary_expressiondelegate_typeの値である場合、invocation_expressionはデリゲート呼び出しです (§12.8.10.4)。 primary_expressionがメソッド グループでもdelegate_typeの値でもない場合は、バインド時エラーが発生します。

省略可能な argument_list (§12.6.2) は、メソッドのパラメーターの値または変数参照を提供します。

invocation_expressionを評価した結果は、次のように分類されます。

  • invocation_expressionが returns-no-value メソッド (§15.6.1) または returns-no-value デリゲートを呼び出した場合、結果は何もありません。 何も分類されていない式は、 statement_expression (§13.7) または lambda_expression の本文 (§12.19) のコンテキストでのみ許可されます。 それ以外の場合は、バインド時エラーが発生します。
  • それ以外の場合、 invocation_expression が returns by-ref メソッド (§15.6.1) または returns by-ref デリゲートを呼び出す場合、結果はメソッドまたはデリゲートの戻り値の型に関連付けられた型を持つ変数になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型 Tの場合、関連付けられている型は、 T で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。
  • それ以外の場合、 invocation_expression は戻り値によるメソッド (§15.6.1) を呼び出すか、値によるデリゲートを返します。結果は、メソッドまたはデリゲートの戻り値の型に関連付けられた値になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型 Tの場合、関連付けられている型は、 T で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。

12.8.10.2 メソッドの呼び出し

メソッド呼び出しの場合、invocation_expressionprimary_expressionはメソッド グループである必要があります。 メソッド グループは、呼び出す 1 つのメソッド、または呼び出す特定のメソッドを選択するオーバーロードされたメソッドのセットを識別します。 後者の場合、呼び出す特定のメソッドの決定は、 argument_listの引数の型によって提供されるコンテキストに基づきます。

フォーム M(A)のメソッド呼び出しのバインド時処理 ( M はメソッド グループ (場合によっては type_argument_listを含む) であり、 A は省略可能な argument_listであり、次の手順で構成されます。

  • メソッド呼び出しの候補メソッドのセットが構築されます。 メソッド グループ Mに関連付けられているメソッド Fごとに、次の手順を実行します。
    • Fが非ジェネリックの場合、Fは次の場合に候補になります。
      • M には型引数リストがありません。
      • F は、 A (§12.6.4.2) に適用されます。
    • Fがジェネリックで、Mに型引数リストがない場合、Fは次の場合に候補になります。
      • 型の推論 (§12.6.3) が成功し、呼び出しの型引数の一覧が推論され、
      • 推論された型引数が対応するメソッド型パラメーターに置き換えられると、 F のパラメーター リスト内のすべての構築された型がその制約 (§8.4.5) を満たし、 F のパラメーター リストが A (§12.6.4.2) に適用されます。
    • Fがジェネリックで、Mに型引数リストが含まれている場合、Fは次の場合に候補になります。
      • F には、型引数リストで指定されたのと同じ数のメソッド型パラメーターがあります。
      • 対応するメソッド型パラメーターに対して型引数が置き換えられると、 F のパラメーター リスト内のすべての構築された型がその制約 (§8.4.5) を満たし、 F のパラメーター リストが A (§12.6.4.2 に対して適用されます。
  • 候補メソッドのセットは、最も派生した型のメソッドのみを含むよう縮小されます。セット内の C.F メソッドごとに、 C はメソッド F が宣言されている型であり、基本型の C で宣言されているすべてのメソッドがセットから削除されます。 さらに、 Cobject以外のクラス型の場合、インターフェイス型で宣言されているすべてのメソッドがセットから削除されます。

    : この後者の規則は、メソッド グループが、 object 以外の有効な基底クラスと空でない有効なインターフェイス セットを持つ型パラメーターに対するメンバー参照の結果である場合にのみ有効です。 end note

  • 結果の候補メソッドのセットが空の場合、次の手順に沿った処理は中止され、代わりに拡張メソッド呼び出しとして呼び出しを処理しようとします (§12.8.10.3)。 これが失敗した場合は、該当するメソッドが存在せず、バインド時エラーが発生します。
  • 候補メソッドのセットの最適なメソッドは、 §12.6.4 のオーバーロード解決規則を使用して識別されます。 1 つの最適なメソッドを識別できない場合、メソッドの呼び出しがあいまいになり、バインド時エラーが発生します。 オーバーロード解決を実行する場合、ジェネリック メソッドのパラメーターは、対応するメソッド型パラメーターの型引数 (指定または推論) を置き換えた後に考慮されます。

上記の手順でバインド時にメソッドを選択して検証すると、実際の実行時呼び出しは、 §12.6.6 で説明されている関数メンバー呼び出しの規則に従って処理されます。

: 上記の解決規則の直感的な効果は次のとおりです。メソッド呼び出しによって呼び出された特定のメソッドを検索するには、メソッド呼び出しによって示される型から開始し、該当する、アクセス可能な、オーバーライドされていないメソッド宣言が少なくとも 1 つ見つかるまで継承チェーンを続行します。 次に、その型で宣言されている、アクセス可能な非オーバーライド メソッドのセットに対して型の推論とオーバーロードの解決を実行し、選択されたメソッドを呼び出します。 メソッドが見つからなかった場合は、代わりに拡張メソッド呼び出しとして呼び出しを処理してみてください。 end note

12.8.10.3 拡張メソッドの呼び出し

いずれかのフォームのメソッド呼び出し (§12.6.6.2) で

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

呼び出しの通常の処理で該当するメソッドが見つからない場合は、拡張メソッド呼び出しとしてコンストラクトを処理しようとします。 «expr» またはいずれかの «args» にコンパイル時の型 dynamicがある場合、拡張メソッドは適用されません。

目的は、対応する静的メソッド呼び出しを実行できるように、最適な type_nameCを見つけることです。

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

拡張メソッドCᵢ.Mₑは、次の場合利用できません。

  • Cᵢ は、非ジェネリックで入れ子になっていないクラスです
  • Mₑの名前は identifier です
  • Mₑ はアクセス可能であり、上記のように静的メソッドとして引数に適用される場合に適用されます
  • expr から Mₑ の最初のパラメーターの型への暗黙的な ID、参照、またはボックス化の変換が存在します。

Cの検索は次のように進みます。

  • 最も近い外側の名前空間宣言から始めて、外側の名前空間宣言を続け、それを含むコンパイル単位で終わるまで、拡張メソッドの候補セットを見つけようとします。
    • 指定された名前空間またはコンパイル ユニットに、対象となる拡張メソッドがMₑCᵢ非ジェネリック型宣言が直接含まれている場合、それらの拡張メソッドのセットが候補セットになります。
    • 指定された名前空間またはコンパイル ユニットの名前空間ディレクティブを使用してインポートされた名前空間に、Mₑ対象の拡張メソッドを含むCᵢ非ジェネリック型宣言が直接含まれている場合、それらの拡張メソッドのセットが候補セットになります。
  • 外側の名前空間宣言またはコンパイル 単位で候補セットが見つからない場合は、コンパイル時エラーが発生します。
  • それ以外の場合は、 §12.6.4 で説明されているように、オーバーロードの解決が候補セットに適用されます。 最適な方法が 1 つも見つからない場合は、コンパイル時エラーが発生します。
  • C は、最適なメソッドが拡張メソッドとして宣言される型です。

Cをターゲットとして使用すると、メソッド呼び出しは静的メソッド呼び出しとして処理されます (§12.6.6)。

: インスタンス メソッドの呼び出しとは異なり、 expr が null 参照に評価されるときに例外はスローされません。 代わりに、この null 値は、通常の静的メソッド呼び出しを介して行われるように拡張メソッドに渡されます。 このような呼び出しに応答する方法は、拡張メソッドの実装によって決められます。 end note

上記の規則は、インスタンス メソッドが拡張メソッドよりも優先されることを意味し、内部名前空間宣言で使用できる拡張メソッドは、外側の名前空間宣言で使用できる拡張メソッドよりも優先され、名前空間で直接宣言された拡張メソッドは、using 名前空間ディレクティブを使用して同じ名前空間にインポートされた拡張メソッドよりも優先されることを意味します。

例:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

この例では、 Bのメソッドが最初の拡張メソッドよりも優先され、 Cのメソッドが両方の拡張メソッドよりも優先されます。

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

この例の出力は次のとおりです。

E.F(1)
D.G(2)
C.H(3)

D.GC.Gよりも優先され、 E.FD.FC.Fの両方よりも優先されます。

end の例

12.8.10.4 デリゲート呼び出し

デリゲート呼び出しの場合、invocation_expressionprimary_expressiondelegate_typeの値である必要があります。 さらに、delegate_typedelegate_typeと同じパラメーター リストを持つ関数メンバーであると考えると、invocation_expressionargument_listに関して、delegate_typeを適用 (§12.6.4.2) する必要があります。

フォーム D(A)のデリゲート呼び出しの実行時処理 (Ddelegate_typeprimary_expressionで、Aは省略可能なargument_list) は、次の手順で構成されます。

  • D が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
  • 引数リスト A が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
  • Dの値が有効であることが確認されます。 Dの値がnull場合、System.NullReferenceExceptionがスローされ、それ以上の手順は実行されません。
  • それ以外の場合、 D はデリゲート インスタンスへの参照です。 関数メンバーの呼び出し (§12.6.6) は、デリゲートの呼び出しリスト内の呼び出し可能な各エンティティに対して実行されます。 インスタンスとインスタンス メソッドで構成される呼び出し可能エンティティの場合、呼び出しのインスタンスは呼び出し可能エンティティに含まれるインスタンスです。

パラメーターのない複数の呼び出しリストの詳細については、 §20.6 を参照してください。

12.8.11 Null 条件呼び出し式

null_conditional_invocation_expressionは構文上、null_conditional_member_access (§12.8.8) またはnull_conditional_element_access (§12.8.13) のいずれかであり、最終的なdependent_accessは呼び出し式 (§12.8.10) です。

null_conditional_invocation_expressionは、statement_expression (§13.7)、anonymous_function_body (§12.19.1)、またはmethod_body (§15.6.1) のコンテキスト内で発生します。

構文上同等の null_conditional_member_accessnull_conditional_element_accessとは異なり、 null_conditional_invocation_expression は何も分類されない場合があります。

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

オプションの null_forgiving_operator は、 null_conditional_member_access または null_conditional_element_accessdelegate_typeがある場合にのみ含まれます。

null_conditional_invocation_expressionEP?A形式です。Aは構文上同等のnull_conditional_member_accessまたはnull_conditional_element_accessの残りの部分であるため、A.または[で始まります。 PAPAの結び付きを示しましょう。

Estatement_expressionとして発生した場合、Eの意味は statement の意味と同じです:

if ((object)P != null) PA

ただし、 P は 1 回だけ評価されます。

Eanonymous_function_bodyまたはmethod_bodyとして発生する場合、Eの意味はその分類によって異なります。

  • Eが何も分類されない場合、その意味は block の意味と同じです:

    { if ((object)P != null) PA; }
    

    ただし、 P は 1 回だけ評価されます。

  • それ以外の場合、 E の意味は、 block の意味と同じです:

    { return E; }
    

    さらに、このブロックの意味はEnull_conditional_member_access (§12.8.8) またはnull_conditional_element_access (§12.8.13) と構文的に等しいかどうかによって異なります。

12.8.12 要素アクセス

12.8.12.1 全般

element_accessは、primary_no_array_creation_expression、"[" トークン、argument_list、その後に "]" トークンで構成されます。 argument_listは、コンマで区切られた 1 つ以上の argument で構成されます。

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

element_accessargument_listには、outまたはref引数を含めることはできません。

次の少なくとも 1 つが保持されている場合、 element_access は動的にバインドされます (§12.3.3)。

  • primary_no_array_creation_expressionにはコンパイル時の型dynamicがあります。
  • argument_listの少なくとも 1 つの式にコンパイル時の型dynamicがあり、primary_no_array_creation_expressionに配列型がありません。

この場合、コンパイラは element_accessdynamic型の値として分類します。 element_accessの意味を判断するための以下の規則は、コンパイル時の型がdynamicprimary_no_array_creation_expression式とargument_list式のコンパイル時型ではなく、実行時の型を使用して実行時に適用されます。 primary_no_array_creation_expressionにコンパイル時の型dynamicがない場合、要素アクセスは、§12.6.5 で説明されているように、制限付きのコンパイル時チェックを受けます。

element_accessprimary_no_array_creation_expressionarray_typeの値である場合、element_accessは配列アクセスです (§12.8.12.2)。 それ以外の場合、 primary_no_array_creation_expression は、1 つ以上のインデクサー メンバーを持つクラス、構造体、またはインターフェイス型の変数または値になります。その場合、 element_access はインデクサー アクセス (§12.8.12.3)。

12.8.12.2 配列アクセス

配列アクセスの場合、element_accessprimary_no_array_creation_expressionarray_typeの値である必要があります。 さらに、配列アクセスの argument_list には、名前付き引数を含めることはできません。 argument_list内の式の数は、array_typeのランクと同じで、各式は、intuintlong、またはulong,の型であるか、またはこれらの型の 1 つ以上に暗黙的に変換できる必要があります。

配列アクセスを評価した結果は、配列の要素型の変数、つまり、 argument_list内の式の値によって選択された配列要素です。

Parray_typeprimary_no_array_creation_expressionであり、Aargument_listであるフォーム P[A]の配列アクセスの実行時処理は、次の手順で構成されます。

  • P が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
  • argument_listのインデックス式は、左から右の順に評価されます。 各インデックス式の評価の後、intuintlongulongのいずれかの型への暗黙的な変換 (§10.2) が実行されます。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 たとえば、インデックス式が型shortの場合、shortからintへの暗黙的な変換と、shortからlongへの暗黙的な変換が可能であるため、intへの暗黙的な変換が実行されます。 インデックス式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上のインデックス式は評価されません。それ以上の手順は実行されません。
  • Pの値が有効であることが確認されます。 Pの値がnull場合、System.NullReferenceExceptionがスローされ、それ以上の手順は実行されません。
  • argument_list内の各式の値は、Pによって参照される配列インスタンスの各次元の実際の境界に対してチェックされます。 1 つ以上の値が範囲外の場合、 System.IndexOutOfRangeException がスローされ、それ以上の手順は実行されません。
  • インデックス式によって指定された配列要素の位置が計算され、この場所が配列アクセスの結果になります。

12.8.12.3 インデクサー アクセス

インデクサー アクセスの場合、element_accessprimary_no_array_creation_expressionはクラス、構造体、またはインターフェイス型の変数または値であり、この型は、element_accessargument_listに適用できる 1 つ以上のインデクサーを実装する必要があります。

Pはクラス、構造体、またはインターフェイス型のTprimary_no_array_creation_expressionであり、Aargument_listであるフォーム P[A]のインデクサー アクセスのバインド時処理は、次の手順で構成されます。

  • Tによって提供されるインデクサーのセットが構築されます。 セットは、Tで宣言されているすべてのインデクサー、または宣言をオーバーライドせず、現在のコンテキスト (§7.5) でアクセスできるTの基本型で構成されます。
  • このセットは、他のインデクサーによって適用され、非表示にならないインデクサーに縮小されます。 次の規則は、セット内の各インデクサー S.I に適用されます。ここで、 S はインデクサー I が宣言されている型です。
    • A (§12.6.4.2) に対してIが適用されない場合、Iはセットから削除されます。
    • IA (§12.6.4.2) に適用できる場合、基本型のSで宣言されているすべてのインデクサーがセットから削除されます。
    • A (§12.6.4.2) に対してIが適用され、Sobject以外のクラス型である場合、インターフェイスで宣言されているすべてのインデクサーがセットから削除されます。
  • 結果の候補インデクサーのセットが空の場合、適用可能なインデクサーは存在せず、バインド時エラーが発生します。
  • 候補インデクサーのセットの最適なインデクサーは、 §12.6.4 のオーバーロード解決規則を使用して識別されます。 1 つの最適なインデクサーを識別できない場合、インデクサーのアクセスがあいまいになり、バインド時エラーが発生します。
  • argument_listのインデックス式は、左から右の順に評価されます。 インデクサー アクセスを処理した結果は、インデクサー アクセスとして分類される式です。 インデクサー アクセス式は、上記の手順で決定されたインデクサーを参照し、 P のインスタンス式と、 Aの関連付けられた引数リストと、インデクサーの型である関連付けられた型を持っています。 Tがクラス型の場合、関連付けられている型は、Tから開始してその基底クラスを検索するときに見つかったインデクサーの最初の宣言またはオーバーライドから選択されます。

使用されるコンテキストに応じて、インデクサー アクセスによって、インデクサーの get アクセサーまたは set アクセサーが呼び出されます。 インデクサー アクセスが割り当てのターゲットである場合は、set アクセサーが呼び出されて新しい値が割り当てられます (§12.21.2)。 それ以外の場合は、get アクセサーが呼び出されて現在の値が取得されます (§12.2.2)。

12.8.13 Null 条件付き要素アクセス

null_conditional_element_accessは、primary_no_array_creation_expressionの後に 2 つのトークン "?" と "[" が続き、その後にargument_listが続き、その後に "]" トークンが続き、その後に 0 個以上のdependent_accessが続き、その前にnull_forgiving_operatorを付けることができます。

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

null_conditional_element_accesselement_accessの条件付きバージョン (§12.8.12) であり、結果の型がvoid場合はバインディング時間エラーです。 結果の型が void 可能性がある null 条件式については、(§12.8.11 を参照してください)。

null_conditional_element_accessEP?[A]B形式です。Bdependent_accessです (存在する場合)。 Eの意味は次のように決定されます。

  • Pの型が null 許容値型の場合:

    TP.Value[A]Bの型にしましょう。

    • Tが参照型または null 非許容値型のいずれでもない型パラメーターである場合、コンパイル時エラーが発生します。

    • Tが null 非許容値型の場合、Eの型はT?され、Eの意味は次の意味と同じです。

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      ただし、 P は 1 回だけ評価されます。

    • それ以外の場合、 E の型は Tされ、 E の意味は次の意味と同じです。

      ((object)P == null) ? null : P.Value[A]B
      

      ただし、 P は 1 回だけ評価されます。

  • それ以外:

    TP[A]Bの型にしましょう。

    • Tが参照型または null 非許容値型のいずれでもない型パラメーターである場合、コンパイル時エラーが発生します。

    • Tが null 非許容値型の場合、Eの型はT?され、Eの意味は次の意味と同じです。

      ((object)P == null) ? (T?)null : P[A]B
      

      ただし、 P は 1 回だけ評価されます。

    • それ以外の場合、 E の型は Tされ、 E の意味は次の意味と同じです。

      ((object)P == null) ? null : P[A]B
      

      ただし、 P は 1 回だけ評価されます。

: 次の形式の式を使用します。

P?[A₀]?[A₁]

Pが評価nullA₀またはA₁のどちらも評価されません。 式が null_conditional_element_access または null_conditional_member_access §12.8.8 操作のシーケンスである場合も同様です。

end note

12.8.14 このアクセス

this_accessはキーワード thisで構成されます。

this_access
    : 'this'
    ;

this_accessは、インスタンス コンストラクター、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはファイナライザーのブロックでのみ許可されます。 次のいずれかの意味があります。

  • クラスのインスタンス コンストラクター内のprimary_expressionthisを使用すると、値として分類されます。 値の型は、使用法が発生するクラスのインスタンス型 (§15.3.2) であり、値は構築されるオブジェクトへの参照です。
  • クラスのインスタンス メソッドまたはインスタンス アクセサー内のprimary_expressionthisを使用すると、値として分類されます。 値の型は、使用法が発生するクラスのインスタンス型 (§15.3.2) であり、値はメソッドまたはアクセサーが呼び出されたオブジェクトへの参照です。
  • 構造体のインスタンス コンストラクター内のprimary_expressionthisを使用すると、変数として分類されます。 変数の型は、使用が行われる構造体のインスタンス型 (§15.3.2) であり、変数は構築される構造体を表します。
    • コンストラクター宣言にコンストラクター初期化子がない場合、 this 変数は構造体型の出力パラメーターとまったく同じように動作します。 特に、これは、インスタンス コンストラクターのすべての実行パスで変数が確実に割り当てられることを意味します。
    • それ以外の場合、 this 変数は構造体型の ref パラメーターとまったく同じように動作します。 特に、これは変数が最初に割り当てられたと見なされることを意味します。
  • 構造体のインスタンス メソッドまたはインスタンス アクセサー内のprimary_expressionthisを使用すると、変数として分類されます。 変数の型は、使用が行われる構造体のインスタンス型 (§15.3.2) です。
    • メソッドまたはアクセサーが反復子 (§15.14) または非同期関数 (§15.15) でない場合、 this 変数はメソッドまたはアクセサーが呼び出された構造体を表します。
      • 構造体が readonly structの場合、 this 変数は構造体型の入力パラメーターとまったく同じように動作します。
      • それ以外の場合、 this 変数は構造体型の ref パラメーターとまったく同じように動作します。
    • メソッドまたはアクセサーが反復子または非同期関数の場合、 this 変数はメソッドまたはアクセサーが呼び出された構造体の コピー を表し、構造体型の value パラメーターとまったく同じように動作します。

上記のコンテキスト以外のコンテキストでprimary_expressionthisを使用すると、コンパイル時エラーになります。 特に、静的メソッド、静的プロパティ アクセサー、またはフィールド宣言のvariable_initializerthisを参照することはできません。

12.8.15 ベース アクセス

base_accessは、キーワード ベースの後に "." トークンと識別子と省略可能なtype_argument_list、または角かっこで囲まれたargument_listで構成されます。

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

base_accessは、現在のクラスまたは構造体の同様の名前付きメンバーによって非表示になっている基底クラスのメンバーにアクセスするために使用されます。 base_accessは、インスタンス コンストラクター、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはファイナライザーの本体でのみ許可されます。 クラスまたは構造体で base.I が発生した場合、そのクラスまたは構造体の基底クラスのメンバーを示します。 同様に、クラスで base[E] が発生した場合、適用可能なインデクサーが基底クラスに存在する必要があります。

バインド時に、フォーム base.Ibase[E]base_access式は、((B)this).I((B)this)[E]が記述された場合とまったく同じように評価されます。ここで、Bはコンストラクトが発生するクラスまたは構造体の基底クラスです。 したがって、 base.Ibase[E]this.Ithis[E]に対応します。ただし、 this は基底クラスのインスタンスと見なされます。

base_accessが仮想関数メンバー (メソッド、プロパティ、またはインデクサー) を参照すると、実行時に呼び出す関数メンバーの決定 (§12.6.6) が変更されます。 呼び出される関数メンバーは、Bに関して関数メンバーの最も派生した実装 (§15.6.4) を見つけることによって決定されます (基本以外のアクセスでは通常と同様に、thisの実行時の型に関してではなく)。 したがって、仮想関数メンバーのオーバーライド内で、 base_access を使用して、関数メンバーの継承された実装を呼び出すことができます。 base_accessによって参照される関数メンバーが抽象である場合は、バインド時エラーが発生します。

: thisとは異なり、 base 自体は式ではありません。 これは、 base_access または constructor_initializer (§15.11.2) のコンテキストでのみ使用されるキーワードです。 end note

12.8.16 後置インクリメントおよびデクリメント演算子

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

後置インクリメントまたはデクリメント演算のオペランドは、変数、プロパティ アクセス、またはインデクサー アクセスとして分類される式でなければなりません。 演算の結果は、オペランドと同じ型の値になります。

primary_expressionにコンパイル時の型dynamicがある場合、演算子は動的にバインドされます (§12.3.3)、post_increment_expressionまたはpost_decrement_expressionにはコンパイル時の型dynamicがあり、primary_expressionの実行時の型を使用して次の規則が実行時に適用されます。

後置インクリメントまたはデクリメント演算のオペランドがプロパティまたはインデクサー アクセスである場合、プロパティまたはインデクサーには get アクセサーと set アクセサーの両方が含まれます。 そうでない場合は、バインド時エラーが発生します。

単項演算子のオーバーロード解決 (§12.4.4) を適用して、特定の演算子の実装を選択します。 定義済みの ++ 演算子と -- 演算子は、 sbytebyteshortushortintuintlongulongcharfloatdoubledecimal、および任意の列挙型に対して存在します。 定義済みの ++ 演算子は、オペランドに 1 を追加することによって生成された値を返し、定義済みの -- 演算子は、オペランドから 1 を減算することによって生成された値を返します。 チェックされたコンテキストでは、この加算または減算の結果が結果型の範囲外で、結果の型が整数型または列挙型の場合、 System.OverflowException がスローされます。

選択した単項演算子の戻り値の型から primary_expressionの型への暗黙的な変換が必要です。それ以外の場合は、コンパイル時エラーが発生します。

フォーム x++ または x-- の後置インクリメントまたはデクリメント操作の実行時処理は、次の手順で構成されます。

  • xが変数として分類される場合:
    • x は、変数を生成するために評価されます。
    • xの値が保存されます。
    • xの保存された値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。
    • 演算子によって返される値は、 x の型に変換され、 xの以前の評価によって指定された場所に格納されます。
    • xの保存された値が操作の結果になります。
  • xがプロパティまたはインデクサー アクセスとして分類されている場合:
    • インスタンス式 (xstaticされていない場合) と、xに関連付けられている引数リスト (xがインデクサー アクセスの場合) が評価され、その結果は後続の get アクセサー呼び出しおよび set アクセサー呼び出しで使用されます。
    • xの get アクセサーが呼び出され、戻り値が保存されます。
    • xの保存された値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。
    • 演算子によって返される値は x の型に変換され、 x の set アクセサーは値引数としてこの値を使用して呼び出されます。
    • xの保存された値が操作の結果になります。

++演算子と--演算子では、プレフィックス表記もサポートされています (§12.9.6)。 x++またはx--の結果は、操作のx 値ですが、++xまたは--xの結果は操作x 値です。 どちらの場合も、 x 自体は操作後に同じ値を持ちます。

演算子 ++ または演算子 -- 実装は、後置表記またはプレフィックス表記を使用して呼び出すことができます。 2 つの表記に対して個別の演算子を実装することはできません。

12.8.17 新しい演算子

12.8.17.1 全般

new演算子は、型の新しいインスタンスを作成するために使用されます。

新しい式には、次の 3 つの形式があります。

  • オブジェクト作成式と匿名オブジェクト作成式は、クラス型と値型の新しいインスタンスを作成するために使用されます。
  • 配列作成式は、配列型の新しいインスタンスを作成するために使用されます。
  • デリゲート作成式は、デリゲート型のインスタンスを取得するために使用されます。

new演算子は、型のインスタンスの作成を意味しますが、必ずしもメモリの割り当てを意味するとは限りません。 特に、値型のインスタンスでは、存在する変数を超える追加のメモリは必要ありません。また、 new を使用して値型のインスタンスを作成するときに割り当ては行われません。

: デリゲート作成式では、常に新しいインスタンスが作成されるわけではありません。 式がメソッド グループ変換 (§10.8) または匿名関数変換 (§10.7) と同じ方法で処理されると、既存のデリゲート インスタンスが再利用される可能性があります。 end note

12.8.17.2 オブジェクト作成式

object_creation_expressionは、class_typeまたはvalue_typeの新しいインスタンスを作成するために使用されます。

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

object_creation_expressionは、class_typevalue_type、またはtype_parameterである必要があります。 typetuple_typeまたは抽象または静的なclass_typeにすることはできません。

省略可能な argument_list (§12.6.2) は、 class_type または struct_typeである場合にのみ許可されます。

オブジェクト作成式には、オブジェクト初期化子またはコレクション初期化子が含まれている場合、コンストラクターの引数リストとかっこを囲む省略できます。 コンストラクター引数リストを省略し、かっこを囲むのは、空の引数リストを指定することと同じです。

オブジェクト初期化子またはコレクション初期化子を含むオブジェクト作成式の処理は、最初にインスタンス コンストラクターを処理してから、オブジェクト初期化子 (§12.8.17.3) またはコレクション初期化子 (§12.8.17.4 で指定されたメンバーまたは要素の初期化を処理します。

省略可能なargument_listのいずれかの引数にコンパイル時の型dynamicがある場合、object_creation_expressionは動的にバインドされ (§12.3.3)、コンパイル時の型がdynamicargument_listの引数の実行時の型を使用して、次の規則が実行時に適用されます。 ただし、オブジェクトの作成では、 §12.6.5 で説明されているように、コンパイル時のチェックが制限されます。

Tclass_typeまたはvalue_typeで、Aが省略可能なargument_listである新しいT(A)object_creation_expressionのバインド時処理は、次の手順で構成されます。

  • Tvalue_typeであり、Aが存在しない場合:
  • それ以外の場合、 Ttype_parameter であり、 A が存在しない場合:
    • Tに値型制約またはコンストラクター制約 (§15.2.5) が指定されていない場合は、バインド時エラーが発生します。
    • object_creation_expressionの結果は、型パラメーターがバインドされているランタイム型の値、つまり、その型の既定のコンストラクターを呼び出した結果です。 実行時の型には、参照型または値型を指定できます。
  • それ以外の場合、 Tclass_type または struct_typeの場合:
    • Tが抽象class_typeまたは静的class_typeの場合は、コンパイル時エラーが発生します。
    • 呼び出すインスタンス コンストラクターは、 §12.6.4 のオーバーロード解決規則を使用して決定されます。 候補インスタンス コンストラクターのセットは、 T で宣言されているすべてのアクセス可能なインスタンス コンストラクターで構成されます。これは A (§12.6.4.2 に適用できます。 候補のインスタンス コンストラクターのセットが空の場合、または 1 つの最適なインスタンス コンストラクターを識別できない場合は、バインド時エラーが発生します。
    • object_creation_expressionの結果は、T型の値、つまり、上記の手順で決定されたインスタンス コンストラクターを呼び出すことによって生成される値です。
    • それ以外の場合、 object_creation_expression は無効であり、バインド時エラーが発生します。

object_creation_expressionが動的にバインドされている場合でも、コンパイル時の型は引き続きT

フォームの新しいT(A)object_creation_expressionの実行時処理 (Tclass_typeまたはstruct_typeで、Aが省略可能なargument_listである場合) は、次の手順で構成されます。

  • Tclass_typeの場合:
    • クラス T の新しいインスタンスが割り当てられます。 新しいインスタンスを割り当てるのに十分なメモリがない場合は、 System.OutOfMemoryException がスローされ、それ以上の手順は実行されません。
    • 新しいインスタンスのすべてのフィールドは、既定値 (§9.3) に初期化されます。
    • インスタンス コンストラクターは、関数メンバー呼び出しの規則に従って呼び出されます (§12.6.6)。 新しく割り当てられたインスタンスへの参照がインスタンス コンストラクターに自動的に渡され、このコンストラクター内からインスタンスにアクセスできます。
  • Tstruct_typeの場合:
    • T型のインスタンスは、一時ローカル変数を割り当てることによって作成されます。 struct_typeのインスタンス コンストラクターは、作成するインスタンスの各フィールドに値を確実に割り当てる必要があるため、一時変数の初期化は必要ありません。
    • インスタンス コンストラクターは、関数メンバー呼び出しの規則に従って呼び出されます (§12.6.6)。 新しく割り当てられたインスタンスへの参照がインスタンス コンストラクターに自動的に渡され、このコンストラクター内からインスタンスにアクセスできます。

12.8.17.3 オブジェクト初期化子

オブジェクト初期化子は、オブジェクトの 0 個以上のフィールド、プロパティ、またはインデックス付き要素の値を指定します。

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

オブジェクト初期化子は、メンバー初期化子のシーケンスで構成され、 { トークンと } トークンで囲み、コンマで区切られます。 各 member_initializer は、初期化のターゲットを指定する必要があります。 identifierは、初期化するオブジェクトのアクセス可能なフィールドまたはプロパティに名前を付けますが、角かっこで囲まれたargument_listは、初期化するオブジェクトのアクセス可能なインデクサーの引数を指定する必要があります。 オブジェクト初期化子が同じフィールドまたはプロパティに対して複数のメンバー初期化子を含めるのはエラーです。

: オブジェクト初期化子は同じフィールドまたはプロパティを複数回設定することは許可されていませんが、インデクサーにはこのような制限はありません。 オブジェクト初期化子には、インデクサーを参照する複数の初期化子ターゲットを含め、同じインデクサー引数を複数回使用することもできます。 end note

initializer_target の後に等号と、式、オブジェクト初期化子、またはコレクション初期化子が続きます。 オブジェクト初期化子内の式で、初期化中の新しく作成されたオブジェクトを参照することはできません。

等号の後に式を指定するメンバー初期化子は、ターゲットへの代入 (§12.21.2) と同じ方法で処理されます。

等号の後にオブジェクト初期化子を指定するメンバー初期化子は、 nested オブジェクト初期化子、つまり埋め込みオブジェクトの初期化です。 フィールドまたはプロパティに新しい値を割り当てる代わりに、入れ子になったオブジェクト初期化子の割り当ては、フィールドまたはプロパティのメンバーへの割り当てとして扱われます。 入れ子になったオブジェクト初期化子は、値型のプロパティや、値型を持つ読み取り専用フィールドには適用できません。

等号の後にコレクション初期化子を指定するメンバー初期化子は、埋め込みコレクションの初期化です。 ターゲット フィールド、プロパティ、またはインデクサーに新しいコレクションを割り当てる代わりに、初期化子で指定された要素がターゲットによって参照されるコレクションに追加されます。 ターゲットは、 §12.8.17.4 で指定された要件を満たすコレクション型である必要があります。

初期化子ターゲットがインデクサーを参照する場合、インデクサーの引数は常に 1 回だけ評価されます。 したがって、引数が使用されていなくても (たとえば、入れ子になった初期化子が空のため)、副作用が評価されます。

: 次のクラスは、2 つの座標を持つ点を表します。

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Pointのインスタンスは、次のように作成および初期化できます。

Point a = new Point { X = 0, Y = 1 };

これは、次の場合と同じ効果があります。

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

ここで、 __a はそれ以外の場合は非表示でアクセスできない一時変数です。

次のクラスは、2 つのポイントから作成された四角形と、 Rectangle インスタンスの作成と初期化を示しています。

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

Rectangleのインスタンスは、次のように作成および初期化できます。

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

これは、次の場合と同じ効果があります。

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

ここで、 __r__p1 、および __p2 は、それ以外の場合は非表示でアクセスできない一時的な変数です。

Rectangleのコンストラクターが 2 つの埋め込みPoint インスタンスを割り当てる場合は、新しいインスタンスを割り当てるのではなく、埋め込みPoint インスタンスを初期化するために使用できます。

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

次のコンストラクトを使用して、新しいインスタンスを割り当てる代わりに、埋め込み Point インスタンスを初期化できます。

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

これは、次の場合と同じ効果があります。

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

end の例

12.8.17.4 コレクション初期化子

コレクション初期化子は、コレクションの要素を指定します。

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

コレクション初期化子は、要素初期化子のシーケンスで構成され、 { トークンと } トークンで囲み、コンマで区切られます。 各要素初期化子は、初期化するコレクション オブジェクトに追加する要素を指定し、 { トークンと } トークンで囲まれた式の一覧で構成され、コンマで区切られます。 単一式要素初期化子は中かっこなしで記述できますが、メンバー初期化子のあいまいさを回避するために代入式にすることはできません。 non_assignment_expression運用は、§12.22 で定義されています。

: コレクション初期化子を含むオブジェクト作成式の例を次に示します。

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

end の例

コレクション初期化子が適用されるコレクション オブジェクトは、 System.Collections.IEnumerable を実装する型であるか、コンパイル時エラーが発生します。 左から右の順序で指定された各要素に対して、通常のメンバー参照が適用され、 Addという名前のメンバーが検索されます。 メンバー参照の結果がメソッド グループでない場合は、コンパイル時エラーが発生します。 それ以外の場合は、要素初期化子の式リストを引数リストとしてオーバーロード解決が適用され、コレクション初期化子によって結果のメソッドが呼び出されます。 したがって、コレクション オブジェクトには、各要素初期化子の名前 Add を持つ適用可能なインスタンスまたは拡張メソッドが含まれている必要があります。

:次に、名前と電話番号の一覧を持つ連絡先を表すクラスと、 List<Contact>の作成と初期化を示します。

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

と同じ効果を持つ

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

ここで、 __clist__c1 、および __c2 は、それ以外の場合は非表示でアクセスできない一時的な変数です。

end の例

12.8.17.5 配列作成式

array_creation_expressionは、array_typeの新しいインスタンスを作成するために使用されます。

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

最初のフォームの配列作成式は、式リストから個々の式を削除した結果として生じる型の配列インスタンスを割り当てます。

: 配列作成式 new int[10,20]int[,]型の配列インスタンスを生成し、新しい int[10][,] 配列作成式は int[][,]型の配列インスタンスを生成します。 end の例

式リスト内の各式は、 intuintlong、または ulongの型であるか、またはこれらの型の 1 つ以上に暗黙的に変換可能である必要があります。 各式の値によって、新しく割り当てられた配列インスタンス内の対応するディメンションの長さが決まります。 配列次元の長さは負の値でないため、式リスト内で負の値を持つ定数式を持つことはコンパイル時エラーです。

アンセーフ コンテキスト (§23.2) を除き、配列のレイアウトは指定されていません。

最初の形式の配列作成式に配列初期化子が含まれている場合、式リスト内の各式は定数で、式リストで指定されたランクと次元の長さは配列初期化子の長さと一致する必要があります。

2 番目または 3 番目の形式の配列作成式では、指定された配列型またはランク指定子のランクは、配列初期化子のランクと一致する必要があります。 個々の次元の長さは、配列初期化子の対応する入れ子レベルの要素の数から推論されます。 したがって、次の宣言の初期化子式

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

と完全に対応します。

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

3 番目の形式の配列作成式は、 単純に型指定された配列作成式と呼ばれます。 これは、配列の要素型が明示的に指定されていないが、配列初期化子の式セットの最も一般的な型 (§12.6.3.15) として決定されることを除いて、2 番目の形式に似ています。 多次元配列 (つまり、rank_specifierに少なくとも 1 つのコンマが含まれている配列) の場合、このセットは、入れ子になったarray_initializerで見つかったすべてので構成されます。

配列初期化子については、 §17.7 で詳しく説明します。

配列作成式を評価した結果は、値、つまり新しく割り当てられた配列インスタンスへの参照として分類されます。 配列作成式の実行時処理は、次の手順で構成されます。

  • expression_listのディメンション長式は、左から右の順に評価されます。 各式の評価の後、intuintlongulongのいずれかの型への暗黙的な変換 (§10.2) が実行されます。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上の式は評価されません。それ以上の手順は実行されません。
  • ディメンションの長さの計算値は、次のように検証されます。1 つ以上の値が 0 未満の場合、 System.OverflowException がスローされ、それ以上の手順は実行されません。
  • 指定された次元の長さを持つ配列インスタンスが割り当てられます。 新しいインスタンスを割り当てるのに十分なメモリがない場合は、 System.OutOfMemoryException がスローされ、それ以上の手順は実行されません。
  • 新しい配列インスタンスのすべての要素は、既定値 (§9.3) に初期化されます。
  • 配列作成式に配列初期化子が含まれている場合、配列初期化子内の各式が評価され、対応する配列要素に割り当てられます。 評価と代入は、式が配列初期化子に書き込まれる順序で実行されます。つまり、要素はインデックスの順序を増やして初期化され、右端の次元が最初に増加します。 特定の式の評価または対応する配列要素への後続の代入によって例外が発生した場合、それ以上の要素は初期化されません (そのため、残りの要素には既定値が設定されます)。

配列作成式では、配列型の要素を含む配列のインスタンス化が可能ですが、このような配列の要素は手動で初期化する必要があります。

: ステートメント

int[][] a = new int[100][];

は、 int[]型の 100 個の要素を持つ 1 次元配列を作成します。 各要素の初期値は null。 同じ配列作成式でサブ配列とステートメントをインスタンス化することはできません。

int[][] a = new int[100][5]; // Error

の場合、コンパイル時エラーが発生します。 サブ配列のインスタンス化は、次のように手動で実行できます。

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

end の例

: 配列の配列に "四角形" 図形がある場合、つまりサブ配列がすべて同じ長さの場合は、多次元配列を使用する方が効率的です。 上記の例では、配列の配列をインスタンス化すると、101 個のオブジェクト (1 つの外側の配列と 100 個のサブ配列) が作成されます。 それに対して、

int[,] a = new int[100, 5];

では、1 つのオブジェクト (2 次元配列) のみが作成され、1 つのステートメントで割り当てが実行されます。

end note

: 暗黙的に型指定された配列作成式の例を次に示します。

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

最後の式では、 intstring も暗黙的に他の型に変換できないため、コンパイル時エラーが発生します。 この場合は、明示的に型指定された配列作成式を使用する必要があります。たとえば、 object[]する型を指定する必要があります。 または、いずれかの要素を共通の基本型にキャストして、推論される要素型にすることができます。

end の例

暗黙的に型指定された配列作成式を匿名オブジェクト初期化子 (§12.8.17.7) と組み合わせて、匿名で型指定されたデータ構造を作成できます。

例:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

end の例

12.8.17.6 デリゲート作成式

delegate_creation_expressionは、delegate_typeのインスタンスを取得するために使用されます。

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

デリゲート作成式の引数は、メソッド グループ、匿名関数、またはコンパイル時の型 dynamic または delegate_typeのいずれかの値である必要があります。 引数がメソッド グループの場合は、メソッドを識別し、インスタンス メソッドの場合はデリゲートを作成するオブジェクトを識別します。 引数が匿名関数の場合、デリゲート ターゲットのパラメーターとメソッド本体を直接定義します。 引数が値の場合、コピーを作成するデリゲート インスタンスを識別します。

にコンパイル時の型dynamicがある場合、delegate_creation_expressionは動的にバインドされ (§12.8.17.6)、式の実行時の型を使用して、以下の規則が実行時に適用されます。 それ以外の場合、ルールはコンパイル時に適用されます。

Ddelegate_typeで、Eである新しいD(E)delegate_creation_expressionのバインド時処理は、次の手順で構成されます。

  • Eがメソッド グループの場合、デリゲート作成式は、EからDへのメソッド グループ変換 (§10.8) と同じ方法で処理されます。

  • Eが匿名関数の場合、デリゲート作成式は、EからDへの匿名関数変換 (§10.7) と同じ方法で処理されます。

  • Eが値の場合、EDと互換性があり (§20.2)、結果は、Eを呼び出す単一エントリの呼び出しリストを持つ新しく作成されたデリゲートへの参照になります。

Ddelegate_typeで、Eである新しいD(E)delegate_creation_expressionの実行時処理は、次の手順で構成されます。

  • Eがメソッド グループの場合、デリゲート作成式は、EからDへのメソッド グループ変換 (§10.8) として評価されます。
  • Eが匿名関数の場合、デリゲートの作成は、E から D への匿名関数変換として評価されます (§10.7)。
  • Edelegate_typeの値である場合:
    • E が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
    • Eの値がnull場合、System.NullReferenceExceptionがスローされ、それ以上の手順は実行されません。
    • Dデリゲート型の新しいインスタンスが割り当てられます。 新しいインスタンスを割り当てるのに十分なメモリがない場合は、 System.OutOfMemoryException がスローされ、それ以上の手順は実行されません。
    • 新しいデリゲート インスタンスは、 Eを呼び出す単一エントリ呼び出しリストで初期化されます。

デリゲートの呼び出しリストは、デリゲートがインスタンス化されるときに決定され、デリゲートの有効期間全体にわたって一定のままになります。 つまり、作成されたデリゲートのターゲット呼び出し可能エンティティを変更することはできません。

: 2 つのデリゲートを組み合わせたり、1 つを別のデリゲートから削除したりすると、新しいデリゲートの結果が得られます。既存のデリゲートの内容は変更されていないことに注意してください。 end note

プロパティ、インデクサー、ユーザー定義演算子、インスタンス コンストラクター、ファイナライザー、または静的コンストラクターを参照するデリゲートを作成することはできません。

: 前述のように、メソッド グループからデリゲートが作成されると、デリゲートのパラメーター リストと戻り値の型によって、どのオーバーロードされたメソッドを選択するかが決まります。 この例では、

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

A.f フィールドは、2 番目のSquare メソッドを参照するデリゲートで初期化されます。これは、そのメソッドがパラメーター リストと戻り値の型と完全に一致するためDoubleFunc。 2 番目の Square メソッドが存在しない場合は、コンパイル時エラーが発生します。

end の例

12.8.17.7 匿名オブジェクト作成式

anonymous_object_creation_expressionは、匿名型のオブジェクトを作成するために使用されます。

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

匿名オブジェクト初期化子は匿名型を宣言し、その型のインスタンスを返します。 匿名型は、 objectから直接継承する名前のないクラス型です。 匿名型のメンバーは、型のインスタンスを作成するために使用される匿名オブジェクト初期化子から推論される読み取り専用プロパティのシーケンスです。 具体的には、フォームの匿名オブジェクト初期化子

new {p₁ =e₁ ,p₂ =e₂ ,pv = ev }

フォームの匿名型を宣言します

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

ここで、各 «Tx» は、対応する式 «ex» の型です。 member_declaratorで使用される式は型を持つ必要があります。 したがって、 member_declarator 内の式が null または匿名関数のコンパイル時エラーになります。

匿名型の名前とその Equals メソッドのパラメーターはコンパイラによって自動的に生成され、プログラム テキストでは参照できません。

同じプログラム内で、同じ名前とコンパイル時の型の一連のプロパティを同じ順序で指定する 2 つの匿名オブジェクト初期化子は、同じ匿名型のインスタンスを生成します。

: この例では

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

p1p2が同じ匿名型であるため、最後の行の割り当てが許可されます。

end の例

匿名型の Equals メソッドと GetHashcode メソッドは、 objectから継承されたメソッドをオーバーライドし、プロパティの EqualsGetHashcode の観点から定義されます。したがって、同じ匿名型の 2 つのインスタンスが等しい場合は、すべてのプロパティが等しい場合にのみ等しくなります。

メンバー宣言子は、単純な名前 (§12.8.4)、メンバー アクセス (§12.8.7)、null 条件付きプロジェクション初期化子 §12.8.8 または基本アクセス (§12.8.15) に省略できます。 これは、 projection 初期化子 と呼ばれ、同じ名前のプロパティの宣言と割り当ての短縮形です。 具体的には、フォームのメンバー宣言子

«identifier»«expr» . «identifier»«expr» ? . «identifier»

は、それぞれ次とまったく同じです。

«identifer» = «identifier»«identifier» = «expr» . «identifier»«identifier» = «expr» ? . «identifier»

したがって、プロジェクション初期化子では、識別子は値と、値が割り当てられているフィールドまたはプロパティの両方を選択します。 直感的に、プロジェクション初期化子は値だけでなく、値の名前も投影します。

12.8.18 typeof 演算子

typeof演算子は、型のSystem.Type オブジェクトを取得するために使用されます。

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

typeof_expressionの最初の形式は、typeofキーワードの後にかっこで囲まれた型で構成されます。 このフォームの式の結果は、指定された型の System.Type オブジェクトです。 特定の型に対して System.Type オブジェクトは 1 つだけ存在します。 つまり、型 Tの場合、 typeof(T) == typeof(T) は常に true になります。 型は dynamicにはできません。

2 番目の形式の typeof_expression は、 typeof キーワードの後に、かっこで囲まれた unbound_type_nameで構成されます。

: unbound_type_nametype_name (§7.8) とよく似ていますが、unbound_type_nameにはtype_nametype_argument_listが含まれるgeneric_dimension_specifierが含まれている点が異なります。 end note

typeof_expressionのオペランドが、unbound_type_nametype_nameの両方の文法を満たす一連のトークンである場合(つまり、generic_dimension_specifiertype_argument_listも含まなかった場合)、トークンのシーケンスはtype_nameと見なされます。 unbound_type_nameの意味は次のように決定されます。

  • generic_dimension_specifierを、各type_argumentと同じ数のコンマとキーワードobjectを持つtype_argument_listに置き換えることで、トークンのシーケンスをtype_nameに変換します。
  • すべての型パラメーター制約を無視しながら、結果の type_nameを評価します。
  • unbound_type_nameは、結果として作成された型 (§8.4) に関連付けられた非連結ジェネリック型に解決されます。

型名が null 許容参照型になるというエラーです。

typeof_expressionの結果は、結果のバインドされていないジェネリック型のSystem.Type オブジェクトです。

3 番目の形式の typeof_expression は、 typeof キーワードの後に、かっこで囲まれた void キーワードで構成されます。 このフォームの式の結果は、型が存在しないことを表す System.Type オブジェクトです。 typeof(void)によって返される型オブジェクトは、任意の型に対して返される型オブジェクトとは異なります。

:この特殊な System.Type オブジェクトは、言語内のメソッドへのリフレクションを可能にするクラス ライブラリで役立ちます。これらのメソッドは、 void メソッドを含む任意のメソッドの戻り値の型を System.Typeのインスタンスと共に表す方法を持つことを望みます。 end note

typeof演算子は、型パラメーターで使用できます。 型名が null 許容参照型であることがわかっていれば、コンパイル時エラーになります。 結果は、型パラメーターにバインドされたランタイム型の System.Type オブジェクトです。 実行時の型が null 許容参照型の場合、結果は対応する null 非許容参照型になります。 typeof演算子は、構築された型またはバインドされていないジェネリック型 (§8.4.4) でも使用できます。 バインドされていないジェネリック型のSystem.Type オブジェクトは、インスタンス型 (§15.3.2) のSystem.Type オブジェクトと同じではありません。 インスタンス型は実行時に常に閉じた構築型であるため、その System.Type オブジェクトは使用中の実行時の型引数に依存します。 一方、バインドされていないジェネリック型には型引数がなく、ランタイム型引数に関係なく同じ System.Type オブジェクトが生成されます。

: 例

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

では、次の出力が生成されます。

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

intSystem.Int32は同じ型であることに注意してください。 typeof(X<>)の結果は型引数に依存しませんが、typeof(X<T>)の結果に依存します。

end の例

12.8.19 sizeof 演算子

sizeof演算子は、特定の型の変数によって占有される 8 ビット バイトの数を返します。 sizeof のオペランドとして指定される型は、 unmanaged_type (§8.8) である必要があります。

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

特定の定義済み型の場合、 sizeof 演算子は、次の表に示すように定数 int 値を生成します。

Expression 結果
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

列挙型 Tの場合、式 sizeof(T) の結果は、上記のように、基になる型のサイズと等しい定数値になります。 その他のすべてのオペランド型の場合、 sizeof 演算子は §23.6.9 で指定されます。

12.8.20 オンおよびオフの演算子

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

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

checked演算子は、チェックされたコンテキスト内の包含式を評価し、unchecked演算子は、チェックされていないコンテキストの包含式を評価します。 checked_expressionまたはunchecked_expressionは、parenthesized_expression (§12.8.5) に正確に対応します。ただし、含まれる式は、指定されたオーバーフロー チェック コンテキストで評価されます。

オーバーフロー チェック コンテキストは、 checked および unchecked ステートメント (§13.12) を使用して制御することもできます。

次の操作は、チェックされた演算子とチェックされていない演算子とステートメントによって確立されたオーバーフロー チェック コンテキストの影響を受けます。

  • オペランドが整数型または列挙型の場合の定義済みの ++ 演算子と -- 演算子 (§12.8.16 および §12.9.6)。
  • オペランドが整数型の場合、定義済みの - 単項演算子 (§12.9.3)。
  • 両方のオペランドが整数型または列挙型の場合、定義済みの +-*、および / 二項演算子 (§12.10)。
  • 整数型または列挙型から別の整数型または列挙型への明示的な数値変換 (§10.3.2)、または float または double から整数型または列挙型への明示的な数値変換。

上記のいずれかの操作で結果が生成され、変換先の型で表すには大きすぎる場合、操作が実行されるコンテキストによって結果の動作が制御されます。

  • checkedコンテキストで、操作が定数式 (§12.23) の場合、コンパイル時エラーが発生します。 それ以外の場合、実行時に操作が実行されると、 System.OverflowException がスローされます。
  • uncheckedコンテキストでは、変換先の型に収まらない上位ビットを破棄することで結果が切り捨てられます。

checkedまたはunchecked演算子またはステートメントで囲まれていない非定数式 (§12.23) (実行時に評価される式) の場合、外部要因 (コンパイラ スイッチや実行環境構成など) がチェック評価を呼び出さない限り、既定のオーバーフロー チェック コンテキストはオフになります。

定数式 (§12.23) (コンパイル時に完全に評価できる式) の場合、既定のオーバーフロー チェック コンテキストは常にチェックされます。 定数式が明示的に unchecked コンテキストに配置されていない限り、式のコンパイル時の評価中にオーバーフローが発生すると、常にコンパイル時エラーが発生します。

匿名関数の本体は、匿名関数が発生する checked または unchecked コンテキストの影響を受けません。

: 次のコード内

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

どちらの式もコンパイル時に評価できないので、コンパイル時エラーは報告されません。 実行時に、 F メソッドは System.OverflowExceptionをスローし、 G メソッドは –727379968 (範囲外の結果の下位 32 ビット) を返します。 H メソッドの動作は、コンパイルの既定のオーバーフロー チェック コンテキストによって異なりますが、Fと同じか、Gと同じです。

end の例

: 次のコード内

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

Fで定数式を評価するときに発生するオーバーフローH、式がcheckedコンテキストで評価されるため、コンパイル時エラーが報告されます。 オーバーフローは、 Gで定数式を評価するときにも発生しますが、評価は unchecked コンテキストで行われるため、オーバーフローは報告されません。

end の例

checked演算子とunchecked演算子は、"(" トークンと ")" トークン内にテキストで含まれる操作のオーバーフロー チェック コンテキストにのみ影響します。 演算子は、含まれている式を評価した結果として呼び出される関数メンバーには影響しません。

: 次のコード内

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

F でcheckedを使用してもMultiplyx * yの評価には影響しないため、x * yは既定のオーバーフロー チェック コンテキストで評価されます。

end の例

unchecked演算子は、符号付き整数型の定数を 16 進表記で記述する場合に便利です。

例:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

上記の 16 進定数はどちらも uint 型です。 定数は int 範囲外であるため、 unchecked 演算子を使用しない場合、 int にキャストするとコンパイル時エラーが発生します。

end の例

: checked 演算子と unchecked 演算子とステートメントを使用すると、プログラマはいくつかの数値計算の特定の側面を制御できます。 ただし、一部の数値演算子の動作は、オペランドのデータ型によって異なります。 たとえば、2 つの小数部を乗算すると、明示的にオフになっているコンストラクト内であっても、常にオーバーフロー時に例外が発生します。 同様に、2 つの浮動小数点を乗算しても、明示的にチェックされたコンストラクト内であっても、オーバーフロー時に例外が発生することはありません。 さらに、他の演算子は、既定か明示的かに関係なく、チェック モードの影響を受けません。 end note

12.8.21 既定値の式

既定値の式を使用して、型の既定値 (§9.3) を取得します。

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

default_literalは既定値 (§9.3) を表します。 型はありませんが、既定のリテラル変換 (§10.2.16) を使用して任意の型に変換できます。

default_value_expressionの結果は、explictly_typed_defaultの明示的な型の既定値 (§9.3)、またはdefault_value_expressionの変換のターゲット型です。

型が次のいずれかである場合、 default_value_expression は定数式 (§12.23) です。

  • 参照型
  • 参照型であることが知られている型パラメーター (§8.2);
  • sbytebyteshortushortintuintlongulongcharfloatdoubledecimalbool,
  • 任意の列挙型。

12.8.22 スタックの割り当て

スタック割り当て式は、実行スタックからメモリ ブロックを割り当てます。 実行スタックは、ローカル変数が格納されるメモリ領域です。 実行スタックはマネージド ヒープの一部ではありません。 ローカル変数ストレージに使用されるメモリは、現在の関数が戻ったときに自動的に回復されます。

スタック割り当て式の安全なコンテキスト規則については、 §16.4.12.7 で説明されています。

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']'
      stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

stackalloc_expressionは、次の 2 つのコンテキストでのみ許可されます。

  1. local_variable_declarationの初期化E (§13.6.2);
  2. 右オペランド E、それ自体がexpression_statementとして発生する単純な代入 (§12.21.2) の式 (§13.7)

どちらのコンテキストでも、 stackalloc_expression は次のようにしか実行できません。

  • E全体。
  • それ自体がE全体であるconditional_expressionの 2 番目または 3 番目のオペランド (§12.18)。

unmanaged_type (§8.8) は、新しく割り当てられた場所に格納される項目の種類を示し、はこれらの項目の数を示します。 これらを組み合わせて、必要な割り当てサイズを指定します。 の型はint型に暗黙的に変換できる必要があります。

スタック割り当てのサイズを負にすることはできません。これは、項目の数を負の値に評価される constant_expression として指定するコンパイル時エラーです。

実行時に割り当てられる項目の数が負の値である場合、動作は未定義です。 0 の場合、割り当ては行われず、返される値は実装定義です。 項目を割り当てるのに十分なメモリがない場合は、 System.StackOverflowException がスローされます。

stackalloc_initializerが存在する場合:

  • unmanaged_typeを省略すると、stackalloc_element_initializerのセットに対する最も一般的な型 (§12.6.3.15) の規則に従って推論されます。
  • constant_expressionを省略すると、stackalloc_element_initializerの数と推定されます。
  • constant_expressionが存在する場合は、stackalloc_element_initializerの数と等しくなります。

stackalloc_element_initializer は、 unmanaged_type (§10.2) への暗黙的な変換を持つ必要があります。 stackalloc_element_initializerは、割り当てられたメモリ内の要素を、インデックス 0 から始まる順に初期化します。 stackalloc_initializerがない場合、新しく割り当てられたメモリの内容は未定義です。

stackalloc_expressionの結果は、Span<T>型のインスタンスです。ここで、Tunmanaged_typeです。

  • Span<T>(§C.3) は ref 構造体型 (§16.2.3) であり、ここでは、型指定された (T) 項目のインデックス可能なコレクションとして、stackalloc_expressionによって割り当てられたブロックのメモリ ブロックを提示します。
  • 結果の Length プロパティは、割り当てられた項目の数を返します。
  • 結果のインデクサー (§15.9) は、割り当てられたブロックの項目に variable_reference (§9.5) を返し、範囲がチェックされます。

: 安全でないコードで発生する場合、 stackalloc_expression の結果は別の型である可能性があります(§23.9を参照してください。 end note

スタック割り当て初期化子は、 catch ブロックまたは finally ブロック (§13.11) では許可されません。

: stackallocを使用して割り当てられたメモリを明示的に解放する方法はありません。 end note

関数メンバーの実行中に作成されたスタック割り当てメモリ ブロックはすべて、その関数メンバーが戻ったときに自動的に破棄されます。

stackalloc演算子を除き、C# はガベージ コレクションされていないメモリを管理するための定義済みのコンストラクトを提供しません。 このようなサービスは、通常、サポート クラス ライブラリによって提供されるか、基になるオペレーティング システムから直接インポートされます。

例:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

span8の場合、stackallocSpan<int>が発生し、暗黙的な演算子によってReadOnlySpan<int>に変換されます。 同様に、 span9の場合、結果の Span<double> は、次に示すように、変換を使用してユーザー定義型 Widget<double> に変換されます。 end の例

12.8.23 nameof 演算子

nameof_expressionは、プログラム エンティティの名前を定数文字列として取得するために使用されます。

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

nameofはキーワードではないため、単純な名前nameofの呼び出しによって、nameof_expressionは常に構文的にあいまいになります。 互換性上の理由から、名前nameofの名前参照 (§12.8.4) が成功した場合、呼び出しが有効かどうかに関係なく、式はinvocation_expressionとして扱われます。 それ以外の場合は nameof_expressionです。

§12.8.4 および §12.8.7 で説明されている規則に従って、コンパイル時にnamed_entityに対して単純な名前とメンバー アクセスの参照が実行。 ただし、 §12.8.4 および §12.8.7 で説明されている参照では インスタンス メンバーが静的コンテキストで見つかったため、エラーが発生しますが、 nameof_expression はそのようなエラーを生成しません。

メソッド グループにtype_argument_listを指定するnamed_entityのコンパイル時エラーです。 型がdynamicnamed_entity_targetのコンパイル時エラーです。

nameof_expressionstring型の定数式であり、実行時には影響しません。 具体的には、その named_entity は評価されず、明確な代入分析のために無視されます (§9.4.4.22)。 その値は、オプションの最終type_argument_listの前のnamed_entityの最後の識別子であり、次のように変換されます。

  • プレフィックス "@" が使用されている場合は削除されます。
  • unicode_escape_sequence は、対応する Unicode 文字に変換されます。
  • すべての formatting_characters が削除されます。

これらは、識別子間の等価性をテストするときに §6.4.3 で適用されるのと同じ変換です。

: System.Collections.Generic名前空間内で宣言List<T>ジェネリック型を想定した、さまざまなnameof式の結果を次に示します。

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

この例の驚くべき部分は、完全な名前空間ではなく "ジェネリック" に nameof(System.Collections.Generic) を解決し、"String" ではなく "TestAlias" に nameof(TestAlias) することです。 end の例

12.8.24 匿名メソッド式

anonymous_method_expressionは、匿名関数を定義する 2 つの方法のうちの 1 つです。 詳細については、 §12.19 で説明します。

12.9 単項演算子

12.9.1 全般

+-! (論理否定§12.9.4 のみ)、~++--、キャスト、およびawait演算子は単項演算子と呼ばれます。

: 後置 null 許容演算子 (§12.8.9)、 !は、コンパイル時とオーバーロードできないだけの性質のため、上記のリストから除外されます。 end note

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) と addressof_expression (§23.6.5) は、安全でないコード (§23) でのみ使用できます。

unary_expressionのオペランドにコンパイル時の型dynamicがある場合は、動的にバインドされます (§12.3.3)。 この場合、 unary_expression のコンパイル時の型は dynamicされ、以下に示す解決は、オペランドの実行時の型を使用して実行時に行われます。

12.9.2 単項プラス演算子

フォーム +xの操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項プラス演算子は次のとおりです。

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

これらの演算子ごとに、結果はオペランドの値にすぎません。

上で定義したリフトされていない定義済みの単項プラス演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.9.3 単項マイナス演算子

フォーム –xの操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項マイナス演算子は次のとおりです。

  • 整数否定:

    int operator –(int x);
    long operator –(long x);
    

    結果は、0 から X を減算することによって計算されます。 Xの値がオペランド型の最小表現可能な値 (intの場合は −2 ²¹、longの場合は -2⁶ ²) の場合、Xの数学的否定はオペランド型内で表されません。 これが checked コンテキスト内で発生した場合、 System.OverflowException がスローされます。 unchecked コンテキスト内で発生した場合、結果はオペランドの値になり、オーバーフローは報告されません。

    否定演算子のオペランドが uint型の場合、 long型に変換され、結果の型が longされます。 例外は、 int−2147483648 (−2 ²¹) を 10 進整数リテラル (§6.4.5.3) として書き込むことが許可される規則です。

    否定演算子のオペランドが ulong型の場合、コンパイル時エラーが発生します。 例外は、 long−9223372036854775808 (-2⁶²) を 10 進整数リテラル (§6.4.5.3) として書き込むことが許可される規則です。

  • 浮動小数点否定:

    float operator –(float x);
    double operator –(double x);
    

    結果は、符号が反転された X の値になります。 xNaN場合、結果もNaN

  • 10 進否定:

    decimal operator –(decimal x);
    

    結果は、0 から X を減算することによって計算されます。 10 進数の否定は、 System.Decimal型の単項マイナス演算子を使用することと同じです。

上で定義したリフトされていない定義済みの単項マイナス演算子のリフト (§12.4.8) フォームも定義済みです。

12.9.4 論理否定演算子

フォーム !xの操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの論理否定演算子が 1 つだけ存在します。

bool operator !(bool x);

この演算子は、オペランドの論理否定を計算します。オペランドが true場合、結果は false。 オペランドが false の場合、結果は true になります。

上記で定義した、リフト解除された定義済みの論理否定演算子のリフト (§12.4.8) フォームも事前に定義されています。

: 同じ字句トークン (!) で表されるプレフィックス論理否定演算子と後置 null 許容演算子 (§12.8.9) は異なります。 end note

12.9.5 ビットごとの補数演算子

フォーム ~xの操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みのビットごとの補数演算子は次のとおりです。

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

これらの各演算子の演算の結果は、 xのビットごとの補数です。

すべての列挙型 E は、次のビットごとの補数演算子を暗黙的に提供します。

E operator ~(E x);

~xを評価した結果(Xは基になる型Uを持つ列挙型Eの式)、(E)(~(U)x)の評価とまったく同じですが、Eへの変換は常にuncheckedコンテキストの場合と同じように実行されます (§12.8.20)。

上で定義した未リフトの定義済みビットごとの補数演算子のリフト (§12.4.8) フォームも定義済みです。

12.9.6 プレフィックスインクリメント演算子とデクリメント演算子

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

プレフィックスインクリメントまたはデクリメント演算のオペランドは、変数、プロパティ アクセス、またはインデクサー アクセスとして分類される式でなければなりません。 演算の結果は、オペランドと同じ型の値になります。

プレフィックスインクリメントまたはデクリメント演算のオペランドがプロパティまたはインデクサーアクセスである場合、プロパティまたはインデクサーは、get アクセサーと set アクセサーの両方を持つ必要があります。 そうでない場合は、バインド時エラーが発生します。

単項演算子のオーバーロード解決 (§12.4.4) を適用して、特定の演算子の実装を選択します。 定義済みの ++ 演算子と -- 演算子は、 sbytebyteshortushortintuintlongulongcharfloatdoubledecimal、および任意の列挙型に対して存在します。 定義済みの ++ 演算子は、オペランドに 1 を追加することによって生成された値を返し、定義済みの -- 演算子は、オペランドから 1 を減算することによって生成された値を返します。 checkedコンテキストでは、この加算または減算の結果が結果型の範囲外で、結果の型が整数型または列挙型の場合、System.OverflowExceptionがスローされます。

選択した単項演算子の戻り値の型から unary_expressionの型への暗黙的な変換が必要です。それ以外の場合は、コンパイル時エラーが発生します。

フォーム ++x または --x のプレフィックスインクリメントまたはデクリメント操作の実行時処理は、次の手順で構成されます。

  • xが変数として分類される場合:
    • x は、変数を生成するために評価されます。
    • xの値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。
    • 演算子によって返される値は、 xの型に変換されます。 結果の値は、 xの評価によって指定された場所に格納されます。
    • が操作の結果になります。
  • xがプロパティまたはインデクサー アクセスとして分類されている場合:
    • インスタンス式 (xstaticされていない場合) と、xに関連付けられている引数リスト (xがインデクサー アクセスの場合) が評価され、その結果は後続の get アクセサー呼び出しおよび set アクセサー呼び出しで使用されます。
    • xの get アクセサーが呼び出されます。
    • get アクセサーによって返される値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。
    • 演算子によって返される値は、 xの型に変換されます。 xの set アクセサーは、値引数としてこの値を使用して呼び出されます。
    • この値は、操作の結果にもなります。

++演算子と--演算子では、後置表記もサポートされます (§12.8.16)。 x++またはx--の結果は、操作の前のxの値ですが、++xまたは--xの結果は、操作後のxの値です。 どちらの場合も、 x 自体は操作後に同じ値を持ちます。

演算子 ++ または演算子 -- 実装は、後置表記またはプレフィックス表記を使用して呼び出すことができます。 2 つの表記に対して個別の演算子を実装することはできません。

前に定義した、リフト解除された定義済みのプレフィックスインクリメント演算子とデクリメント演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.9.7 キャスト式

cast_expressionは、式を明示的に特定の型に変換するために使用されます。

cast_expression
    : '(' type ')' unary_expression
    ;

フォーム (T)Ecast_expression(Tは型、Eunary_expression)、Eの値の明示的な変換 (§10.3) を実行TEからTへの明示的な変換が存在しない場合は、バインド時エラーが発生します。 それ以外の場合、明示的な変換によって生成される値が結果になります。 Eが変数を表している場合でも、結果は常に値として分類されます。

cast_expressionの文法は、特定の構文のあいまいさにつながります。

: 式(x)–yは、cast_expression (x型への–yのキャスト) またはparenthesized_expression (値x – yを計算する) と組み合わせたadditive_expressionとして解釈できます。 end の例

cast_expressionあいまいさを解決するために、次の規則が存在します。かっこで囲まれた 1 つ以上のトークン (§6.4) のシーケンスは、次の少なくとも 1 つが該当する場合にのみ、cast_expressionの開始と見なされます。

  • トークンのシーケンスは型に対して正しい文法ですが、式には適していません。
  • トークンのシーケンスは型の正しい文法であり、終わりかっこの直後のトークンはトークン "~" です。 トークン "!"、トークン "("、識別子 (§6.4.3)、リテラル (§6.4.5)、または任意のキーワード (§6.4.4) ( as および isを除く)。

上記の "正しい文法" という用語は、トークンのシーケンスが特定の文法の生成に準拠する必要があることを意味します。 具体的には、構成識別子の実際の意味は考慮されません。

: xyが識別子の場合、x.yが実際に型を表していない場合でも、x.yは型の正しい文法になります。 end の例

: xyが識別子、(x)y(x)(y)、および(x)(-y)cast_expressionである場合、xが型を識別する場合でも、(x)-yは無効になります。 ただし、 x が定義済みの型 ( int など) を識別するキーワードである場合、4 つのフォームはすべて cast_expressionされます (このようなキーワードが単独で式にできない可能性があるため)。 end note

12.9.8 Await 式

12.9.8.1 全般

await演算子は、オペランドによって表される非同期操作が完了するまで、外側の非同期関数の評価を中断するために使用されます。

await_expression
    : 'await' unary_expression
    ;

await_expressionは、非同期関数 (§15.15) の本体でのみ許可されます。 最も近い外側の非同期関数内では、 await_expression は次の場所では発生しません。

  • 入れ子になった (非同期ではない) 匿名関数内
  • lock_statementのブロック内
  • 匿名関数から式ツリー型への変換 (§10.7.3)
  • 安全でないコンテキストの場合

: query_expression内のほとんどの場所でawait_expressionは発生できません。非同期でないラムダ式を使用するように構文的に変換されるためです。 end note

非同期関数内では、 awaitavailable_identifier として使用されませんが、逐語的な識別子 @await を使用できます。 したがって、 await_expressionと識別子を含むさまざまな式の間に構文的なあいまいさはありません。 非同期関数の外部では、 await は通常の識別子として機能します。

await_expressionのオペランドは、task と呼ばれます。 これは、 await_expression が評価されるときに完了する場合と完了しない場合がある非同期操作を表します。 await演算子の目的は、待機中のタスクが完了するまで外側の非同期関数の実行を中断し、その結果を取得することです。

12.9.8.2 Awaitable 式

await_expressionのタスクは、可能である必要があります。 次のいずれかが保持されている場合、式 t は待機可能です。

  • t はコンパイル時の型です dynamic
  • t には、パラメーターがなく、型パラメーターを持たない GetAwaiter と呼ばれる、アクセス可能なインスタンスまたは拡張メソッドがあり、次のすべてが保持される戻り値の型 A があります。
    • A は、インターフェイス System.Runtime.CompilerServices.INotifyCompletion を実装します (以下、簡潔にするために INotifyCompletion と呼ばれます)
    • A には、アクセス可能で読み取り可能なインスタンス プロパティ IsCompleted 型があります bool
    • A には、パラメーターと型パラメーターを持たないアクセス可能なインスタンス メソッド GetResult があります

GetAwaiterメソッドの目的は、タスクの awaiter を取得することです。 A型は、await 式の awaiter 型と呼ばれます。

IsCompleted プロパティの目的は、タスクが既に完了しているかどうかを判断することです。 その場合、評価を中断する必要はありません。

INotifyCompletion.OnCompletedメソッドの目的は、タスクに "継続" をサインアップすることです。つまり、タスクが完了すると呼び出されるデリゲート (System.Action型)。

GetResultメソッドの目的は、完了したタスクの結果を取得することです。 この結果は、正常に完了した場合や、結果値がある場合や、 GetResult メソッドによってスローされる例外である可能性があります。

12.9.8.3 await 式の分類

await t は、式 (t).GetAwaiter().GetResult()と同じように分類されます。 したがって、 GetResult の戻り値の型が void場合、 await_expression は何も分類されません。 void以外の戻り値の型Tがある場合、await_expressionT型の値として分類されます。

12.9.8.4 await 式の実行時評価

実行時に、式 await t は次のように評価されます。

  • awaiter a は、式 (t).GetAwaiter()を評価することによって取得されます。
  • boolbは、式(a).IsCompletedを評価することによって取得されます。
  • bfalse場合、評価は、インターフェイスSystem.Runtime.CompilerServices.ICriticalNotifyCompletionを実装aかどうかによって異なります (ここでは簡潔にするためにICriticalNotifyCompletionと呼ばれます)。 このチェックはバインド時に行われます。つまり、 a がコンパイル時の型 dynamicを持つ場合は実行時、それ以外の場合はコンパイル時です。 再開デリゲート r 示します (§15.15)。
    • aICriticalNotifyCompletionを実装していない場合は、式((a) as INotifyCompletion).OnCompleted(r)が評価されます。
    • aICriticalNotifyCompletionを実装している場合、式((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)が評価されます。
    • 評価が中断され、非同期関数の現在の呼び出し元に制御が返されます。
  • 直後 ( btrueされた場合) または再開デリゲートの後の呼び出し時 ( bfalseされた場合)、式 (a).GetResult() が評価されます。 値が返された場合、その値は await_expressionの結果になります。 それ以外の場合、結果は何もありません。

INotifyCompletion.OnCompletedおよびICriticalNotifyCompletion.UnsafeOnCompletedインターフェイス メソッドの awaiter の実装により、デリゲートrが最大 1 回呼び出されます。 それ以外の場合、外側の非同期関数の動作は未定義です。

12.10 算術演算子

12.10.1 全般

*/%+、および-演算子は算術演算子と呼ばれます。

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

算術演算子のオペランドにコンパイル時の型 dynamicがある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamicされ、コンパイル時型が dynamicされているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。

12.10.2 乗算演算子

フォーム x * yの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの乗算演算子を次に示します。 演算子はすべて、 xyの積を計算します。

  • 整数乗算:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    checkedコンテキストでは、製品が結果の種類の範囲外にある場合、System.OverflowExceptionがスローされます。 uncheckedコンテキストでは、オーバーフローは報告されず、結果の種類の範囲外の重要な上位ビットは破棄されます。

  • 浮動小数点乗算:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    製品は IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、xy は正の有限値です。 zx * yの結果であり、最も近い表現可能な値に丸められます。 結果の大きさが変換先の型に対して大きすぎる場合、 z は無限大です。 丸めのために、xyもゼロでなくても、zは 0 になる可能性があります。

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (特に明記されていない限り、 §12.10.2§12.10.6 の浮動小数点テーブルでは "+" を使用すると、値が正であることを意味します。"-" を使用すると、値が負であることを意味し、符号の欠如は正または負の符号 (NaN) であることを意味します)。

  • 10 進乗算:

    decimal operator *(decimal x, decimal y);
    

    結果の値の大きさが 10 進形式で表すには大きすぎる場合は、 System.OverflowException がスローされます。 丸めのため、どちらのオペランドも 0 でない場合でも、結果は 0 になる可能性があります。 丸める前の結果のスケールは、2 つのオペランドのスケールの合計です。 10 進数乗算は、 System.Decimal型の乗算演算子を使用することと同じです。

上記で定義した、リフトされていない定義済みの乗算演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.10.3 除算演算子

フォーム x / yの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの除算演算子を次に示します。 演算子はすべて、 xyの商を計算します。

  • 整数除算:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    右オペランドの値が 0 の場合、 System.DivideByZeroException がスローされます。

    除算は結果を 0 に丸めます。 したがって、結果の絶対値は、2 つのオペランドの商の絶対値以下の最大の整数になります。 2 つのオペランドが同じ符号を持ち、2 つのオペランドが反対の符号を持つ場合、0 または負の場合、結果は 0 または正になります。

    左オペランドが最も小さい表現可能な int または long 値で、右オペランドが –1場合、オーバーフローが発生します。 checkedコンテキストでは、これによりSystem.ArithmeticException (またはそのサブクラス) がスローされます。 uncheckedコンテキストでは、System.ArithmeticException (またはそのサブクラス) がスローされるか、オーバーフローが左オペランドの結果の値と共にレポートされないかどうかについて実装定義されます。

  • 浮動小数点除算:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    商は IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、xy は正の有限値です。 zx / yの結果であり、最も近い表現可能な値に丸められます。

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • 10 進除算:

    decimal operator /(decimal x, decimal y);
    

    右オペランドの値が 0 の場合、 System.DivideByZeroException がスローされます。 結果の値の大きさが 10 進形式で表すには大きすぎる場合は、 System.OverflowException がスローされます。 丸めのため、最初のオペランドが 0 でない場合でも、結果は 0 になる可能性があります。 丸める前の結果のスケールは、正確な結果と同じ結果を保持する優先スケールに最も近いスケールです。 推奨されるスケールは、yのスケールx小さいスケールです。

    10 進数除算は、 System.Decimal型の除算演算子を使用することと同じです。

上記で定義したリフトされていない定義済み除算演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.10.4 剰余演算子

フォーム x % yの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの剰余演算子を次に示します。 演算子はすべて、 xyの間の除算の剰余を計算します。

  • 整数の剰余:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    x % yの結果は、x – (x / y) * yによって生成される値です。 yが 0 の場合、System.DivideByZeroExceptionがスローされます。

    左オペランドが最小のintまたはlong値で、右オペランドが–1場合、x / yが例外をスローする場合にのみ、System.OverflowExceptionがスローされます。

  • 浮動小数点剰余:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、xy は正の有限値です。 zx % y の結果であり、 x – n * yとして計算されます。ここで、n は x / y以下の最大の整数です。 剰余を計算するこの方法は整数オペランドに使用される方法と似ていますが、IEC 60559 定義 ( nx / yに最も近い整数) とは異なります。

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • 10 進剰

    decimal operator %(decimal x, decimal y);
    

    右オペランドの値が 0 の場合、 System.DivideByZeroException がスローされます。 これは、 System.ArithmeticException (またはそのサブクラス) がスローされたときに実装定義されます。 準拠する実装は、x / yが例外をスローしない場合でも、x % yの例外をスローしません。 丸める前の結果の小数点以下桁数は、2 つのオペランドのスケールの大きい方であり、0 以外の場合は結果の符号は xのスケールと同じです。

    10 進剰余は、 System.Decimal型の剰余演算子を使用することと同じです。

    : これらの規則により、すべての型に対して、結果が左オペランドの反対の符号を持つことはありません。 end note

上記で定義した、リフト解除された定義済みの剰余演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.10.5 加算演算子

フォーム x + yの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの加算演算子を次に示します。 数値型と列挙型の場合、定義済みの加算演算子は、2 つのオペランドの合計を計算します。 一方または両方のオペランドが string型の場合、定義済みの加算演算子はオペランドの文字列表現を連結します。

  • 整数の加算:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    checkedコンテキストでは、合計が結果の型の範囲外の場合、System.OverflowExceptionがスローされます。 uncheckedコンテキストでは、オーバーフローは報告されず、結果の種類の範囲外の重要な上位ビットは破棄されます。

  • 浮動小数点加算:

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    合計は IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、xy は 0 以外の有限値であり、zx + y の結果です。 xy が同じ大きさで符号が逆の場合、z は正のゼロになります。 x + yが大きすぎて変換先の型で表すには、zx + yと同じ符号を持つ無限大です。

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • 10 進加算:

    decimal operator +(decimal x, decimal y);
    

    結果の値の大きさが 10 進形式で表すには大きすぎる場合は、 System.OverflowException がスローされます。 丸める前の結果のスケールは、2 つのオペランドのスケールの方が大きくなります。

    10 進加算は、 System.Decimal型の加算演算子を使用することと同じです。

  • 列挙体の追加。 すべての列挙型は、次の定義済みの演算子を暗黙的に提供します。ここで、 E は列挙型、 UEの基になる型です。

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    実行時に、これらの演算子は (E)((U)x + (U)y) とまったく同じように評価されます。

  • 文字列連結:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    バイナリ + 演算子のこれらのオーバーロードは、文字列連結を実行します。 文字列連結のオペランドが null場合は、空の文字列が置き換えられます。 それ以外の場合、string以外のオペランドは、object型から継承された仮想ToString メソッドを呼び出すことによって、その文字列形式に変換されます。 ToStringnullを返す場合は、空の文字列が置換されます。

    例:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    コメントに表示される出力は、米国英語システムの一般的な結果です。 正確な出力は、実行環境のリージョン設定によって異なります。 文字列連結演算子自体はいずれの場合も同じように動作しますが、実行中に暗黙的に呼び出される ToString メソッドは、リージョン設定の影響を受ける可能性があります。

    end の例

    文字列連結演算子の結果は、左オペランドの文字とそれに続く右オペランドの文字で構成される string です。 文字列連結演算子は、 null 値を返しません。 結果の文字列を割り当てるのに十分なメモリがない場合は、 System.OutOfMemoryException がスローされる可能性があります。

  • デリゲートの組み合わせ。 すべてのデリゲート型は、次の定義済みの演算子を暗黙的に提供します。ここで、 D はデリゲート型です。

    D operator +(D x, D y);
    

    最初のオペランドが null場合、演算の結果は 2 番目のオペランドの値になります ( null場合でも)。 それ以外の場合、2 番目のオペランドが null場合、演算の結果は最初のオペランドの値になります。 それ以外の場合、操作の結果は、呼び出しリストが最初のオペランドの呼び出しリスト内の要素で構成され、その後に 2 番目のオペランドの呼び出しリスト内の要素が続く新しいデリゲート インスタンスになります。 つまり、結果のデリゲートの呼び出しリストは、2 つのオペランドの呼び出しリストを連結したものです。

    : デリゲートの組み合わせの例については、 §12.10.6 および §20.6 を参照してください。 System.Delegateはデリゲート型ではないため、演算子 + は定義されていません。end note

上で定義したリフト解除された定義済み加算演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.10.6 減算演算子

フォーム x – yの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの減算演算子を次に示します。 演算子はすべて、xからyを減算します。

  • 整数減算:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    checkedコンテキストでは、差が結果の型の範囲外の場合、System.OverflowExceptionがスローされます。 uncheckedコンテキストでは、オーバーフローは報告されず、結果の種類の範囲外の重要な上位ビットは破棄されます。

  • 浮動小数点減算:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    違いは IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、xy は 0 以外の有限値であり、zx – y の結果です。 xy が等しい場合、z は正のゼロになります。 x – yが大きすぎて変換先の型で表すには、zx – yと同じ符号を持つ無限大です。

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (上の表では、-yエントリは負の値ではなく、yを示しています)。

  • 10 進減算:

    decimal operator –(decimal x, decimal y);
    

    結果の値の大きさが 10 進形式で表すには大きすぎる場合は、 System.OverflowException がスローされます。 丸める前の結果のスケールは、2 つのオペランドのスケールの方が大きくなります。

    10 進減算は、 System.Decimal型の減算演算子を使用することと同じです。

  • 列挙減算。 すべての列挙型は、次の定義済みの演算子を暗黙的に提供します。ここで、 E は列挙型、 UEの基になる型です。

    U operator –(E x, E y);
    

    この演算子は、 (U)((U)x – (U)y)とまったく同じように評価されます。 つまり、演算子は、 xyの序数値の差を計算し、結果の型は列挙型の基になる型になります。

    E operator –(E x, U y);
    

    この演算子は、 (E)((U)x – y)とまったく同じように評価されます。 言い換えると、演算子は列挙型の基になる型から値を減算し、列挙型の値を生成します。

  • デリゲートの削除。 すべてのデリゲート型は、次の定義済みの演算子を暗黙的に提供します。ここで、 D はデリゲート型です。

    D operator –(D x, D y);
    

    セマンティクスは次のとおりです。

    • 最初のオペランドが null の場合は、演算結果は null になります。
    • それ以外の場合、2 番目のオペランドが null場合、演算の結果は最初のオペランドの値になります。
    • それ以外の場合、両方のオペランドは空でない呼び出しリスト (§20.2) を表します。
      • デリゲート等値演算子 (§12.12.9) によって決定されたリストが等しい場合、操作の結果は null
      • それ以外の場合、2 番目のオペランドのリストが最初のオペランドのサブリストである場合、演算の結果は、2 番目のオペランドのエントリが削除された最初のオペランドのリストで構成される新しい呼び出しリストになります。 (サブリストの等価性を判断するために、対応するエントリはデリゲート等値演算子と比較されます)。2 番目のオペランドのリストが、最初のオペランドのリスト内の連続するエントリの複数のサブリストと一致する場合、連続するエントリの最後に一致するサブリストが削除されます。
      • それ以外の場合、演算の結果は左オペランドの値になります。

    オペランドのリスト (存在する場合) のどちらもプロセスで変更されません。

    例:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    end の例

上で定義したリフト解除された定義済みの減算演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.11 シフト演算子

<<演算子と>>演算子は、ビットシフト操作を実行するために使用されます。

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

shift_expressionのオペランドにコンパイル時の型dynamicがある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamicされ、コンパイル時型が dynamicされているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。

フォーム x << count または x >> countの操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

オーバーロードされたシフト演算子を宣言する場合、最初のオペランドの型は常に演算子宣言を含むクラスまたは構造体になり、2 番目のオペランドの型は常に int

定義済みのシフト演算子を次に示します。

  • 左シフト:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    <<演算子x、以下で説明するように計算されたビット数だけ左にシフトします。

    結果の種類の x の範囲外の上位ビットは破棄され、残りのビットは左にシフトされ、下位の空のビット位置は 0 に設定されます。

  • 右シフト:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    >>演算子は、次に示すように計算されたビット数だけ右xシフトします。

    xint型またはlong型の場合、xの下位ビットは破棄され、残りのビットは右にシフトされ、xが負でない場合は空の上位ビット位置が 0 に設定され、xが負の場合は 1 に設定されます。

    xuint型またはulong型の場合、xの下位ビットは破棄され、残りのビットは右にシフトされ、上位の空のビット位置は 0 に設定されます。

定義済みの演算子の場合、シフトするビット数は次のように計算されます。

  • xの型がintまたはuintの場合、シフト数は下位 5 ビットのcountによって与えられます。 つまり、シフト数は count & 0x1Fから計算されます。
  • xの型がlongまたはulongの場合、シフトカウントはcountの下位 6 ビットによって与えられます。 つまり、シフト数は count & 0x3Fから計算されます。

結果のシフト数が 0 の場合、シフト演算子は単に xの値を返します。

シフト演算が原因でオーバーフローが発生することはなく、checked と unchecked のコンテキストで同じ結果が生成されることはありません。

>>演算子の左オペランドが符号付き整数型の場合、演算子は順序指定シフトを実行し、オペランドの最上位ビット (符号ビット) の値が上位の空のビット位置に伝達されます。 >>演算子の左オペランドが符号なし整数型の場合、演算子は論理的なシフト右シフトを実行します。ここで、高次の空のビット位置は常に 0 に設定されます。 オペランド型から推論された逆の演算を実行するには、明示的なキャストを使用できます。

: xint型の変数である場合、操作unchecked ((int)((uint)x >> y))xの論理シフト権限を実行します。 end の例

上記で定義したリフトされていない定義済みシフト演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.12 関係演算子と型テスト演算子

12.12.1 全般

==!=<><=>=is、およびas演算子は、リレーショナル演算子と型テスト演算子と呼ばれます。

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

: is 演算子の右オペランドを検索するには、最初に 、次に複数のトークンにまたがる可能性がある としてテストする必要があります。 オペランドが expreesion である場合パターン式は、少なくとも shift_expressionほど高い優先順位を持つ必要があります。 end note

is演算子については §12.12.12 で説明し、as演算子については §12.12.13 で説明します。

==!=<><=>=の演算子は、comparison 演算子です

<><=、または>=演算子のオペランドとしてdefault_literal (§12.8.21) を使用すると、コンパイル時エラーが発生します。 ==演算子または!=演算子の両方のオペランドとしてdefault_literalを使用すると、コンパイル時エラーが発生します。 isまたはas演算子の左オペランドとしてdefault_literalを使用すると、コンパイル時エラーが発生します。

比較演算子のオペランドにコンパイル時の型 dynamicがある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamicされ、コンパイル時の型が dynamicされているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。

フォーム x «op» yの演算 («op» は比較演算子) の場合、オーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 equality_expressionの両方のオペランドがnullリテラルの場合、オーバーロードの解決は実行されず、演算子が==!=かに応じて、式はtrueまたはfalseの定数値に評価されます。

定義済みの比較演算子については、次のサブクラスで説明します。 次の表に示すように、定義済みのすべての比較演算子はブール型の結果を返します。

操作 結果
x == y truexyと等しい場合はfalse。それ以外の場合は
x != y truexyと等しくない場合はfalseそれ以外の場合
x < y xy より小さい場合は true、それ以外の場合は false
x > y xy より大きい場合は true、それ以外の場合は false
x <= y xy 以下の場合は true、それ以外の場合は false
x >= y xy 以上の場合は true、それ以外の場合は false

12.12.2 整数比較演算子

定義済みの整数比較演算子は次のとおりです。

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

これらの各演算子は、2 つの整数オペランドの数値を比較し、特定の関係がtruefalseかを示すbool値を返します。

上記で定義したリフトされていない定義済みの整数比較演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.12.3 浮動小数点比較演算子

定義済みの浮動小数点比較演算子は次のとおりです。

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

演算子は、IEC 60559 標準の規則に従ってオペランドを比較します。

いずれかのオペランドが NaN の場合、結果がtrue!=を除くすべての演算子に対して結果がfalseされます。 2 つのオペランドの場合、 x != y は常に !(x == y)と同じ結果を生成します。 ただし、一方または両方のオペランドが NaN の場合、<><=、および>=演算子は逆演算子の論理否定と同じ結果を生成しません。

: xy のいずれかが NaN の場合、 x < yfalseされますが、 !(x >= y)trueend の例

どちらのオペランドも NaN でない場合、演算子は 2 つの浮動小数点オペランドの値を順序付けと比較します

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

ここで、 minmax は、指定された浮動小数点形式で表すことができる最小および最大の正の有限値です。 この順序の主な影響は次のとおりです。

  • 負のゼロと正のゼロは等しいと見なされる。
  • 負の無限大は、他のすべての値より小さいと見なされますが、別の負の無限大と等しくなります。
  • 正の無限大は、他のすべての値より大きいと見なされますが、別の正の無限大と等しくなります。

上記で定義した、リフトされていない定義済みの浮動小数点比較演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.12.4 10 進数比較演算子

定義済みの 10 進比較演算子は次のとおりです。

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

これらの各演算子は、2 つの 10 進オペランドの数値を比較し、特定の関係がtruefalseかを示すbool値を返します。 各 10 進比較は、 System.Decimal型の対応するリレーショナル演算子または等値演算子を使用することと同じです。

上記で定義したリフトされていない定義済みの 10 進数比較演算子のリフト (§12.4.8) フォームも定義済みです。

12.12.5 ブール等値演算子

定義済みのブール等値演算子は次のとおりです。

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

==の結果は、xyの両方がtrueされている場合、またはxyの両方がfalseされている場合にtrueされます。 それ以外の場合、結果は false です。

!=の結果は、xyの両方がtrueされている場合、またはxyの両方がfalseされている場合にfalseされます。 それ以外の場合、結果は true です。 オペランドが bool型の場合、 != 演算子は ^ 演算子と同じ結果を生成します。

上記で定義したリフトされていない定義済みのブール等値演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.12.6 列挙比較演算子

すべての列挙型は、次の定義済みの比較演算子を暗黙的に提供します。

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

x «op» yを評価した結果、x と y は基になる型Uを持つ列挙型Eの式であり、«op» は比較演算子の 1 つであり、((U)x) «op» ((U)y)の評価とまったく同じです。 つまり、列挙型の比較演算子は、単に 2 つのオペランドの基になる整数値を比較します。

上記で定義したリフトされていない定義済み列挙比較演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.12.7 参照型の等値演算子

すべてのクラス型 C は、次の定義済みの参照型の等値演算子を暗黙的に提供します。

bool operator ==(C x, C y);
bool operator !=(C x, C y);

定義済みの等値演算子が C に存在しない限り (たとえば、 Cstring または System.Delegateの場合)。

演算子は、等しいか等しくないかの 2 つの参照を比較した結果を返します。 operator ==は、xyが同じインスタンスを参照している場合、または両方がnull場合にのみtrueを返しますが、operator !=は同じオペランドを持つoperator ==falseを返す場合にのみtrueを返します。

標準の適用規則 (§12.6.4.2) に加えて、定義済みの参照型の等値演算子を適用するには、次のいずれかが必要です。

  • どちらのオペランドも、 reference_type またはリテラル nullであることが知られている型の値です。 さらに、どちらかのオペランドから他方のオペランドの型への ID または明示的な参照変換 (§10.3.5) が存在します。
  • 1 つのオペランドはリテラル nullで、もう 1 つのオペランドは T 型の値です。ここで、 T は値型とは認識されず、値型制約を持たない type_parameter です。
    • 実行時に T が null 非許容値型の場合、 == の結果は false され、 != の結果は true
    • 実行時 T が null 許容値型の場合、結果はオペランドの HasValue プロパティから計算されます (§12.12.10 で説明されています)。
    • 実行時にTが参照型の場合、オペランドがnullの場合は結果がtrueされ、それ以外の場合はfalse

これらの条件のいずれかが当てはまる場合を除き、バインド時エラーが発生します。

: これらのルールの主な影響は次のとおりです。

  • 定義済みの参照型の等値演算子を使用して、バインド時に異なることがわかっている 2 つの参照を比較するのは、バインド時エラーです。 たとえば、オペランドのバインディング時の型が 2 つのクラス型であり、どちらも他方から派生していない場合、2 つのオペランドが同じオブジェクトを参照することはできません。 したがって、操作はバインド時エラーと見なされます。
  • 定義済みの参照型の等値演算子では、値型オペランドの比較は許可されません (型パラメーターが特別に処理される nullと比較される場合を除く)。
  • 定義済みの参照型の等値演算子のオペランドはボックス化されません。 新しく割り当てられたボックス化されたインスタンスへの参照は、必ずしも他のすべての参照と異なるため、このようなボックス化操作を実行することは意味がありません。

フォーム x == y または x != yの操作の場合、該当するユーザー定義の operator == または operator != が存在する場合、演算子オーバーロード解決規則 (§12.4.5) は、定義済みの参照型の等値演算子の代わりにその演算子を選択します。 一方または両方のオペランドを明示的にキャストして、定義済みの参照型の等値演算子を常に選択 object

end note

: 次の例では、制約のない型パラメーター型の引数が nullされているかどうかを確認します。

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

x == nullコンストラクトは、Tが null 非許容値型を表す可能性があり、Tが null 非許容値型の場合にfalseするように単純に定義されている場合でも許可されます。

end の例

フォーム x == y または x != yの操作で、該当する operator == または operator != が存在する場合、演算子オーバーロードの解決 (§12.4.5) ルールは、定義済みの参照型の等値演算子ではなく、その演算子を選択します。

: 両方のオペランドを明示的に object型にキャストすることで、定義済みの参照型の等値演算子を常に選択できます。 end note

: 例

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

この例では、次のように出力されます。

True
False
False
False

s変数とt変数は、同じ文字を含む 2 つの異なる文字列インスタンスを参照します。 最初の比較では、定義済みの文字列等値演算子 (§12.12.8) が両方のオペランドがstring型である場合に選択されるため、True出力されます。 残りの比較では、string型のoperator ==のオーバーロードは、いずれかのオペランドにバインド時型のobjectがある場合は適用できないため、すべての出力Falseされます。

上記の手法は、値型では意味を持たないことに注意してください。 例を示します。

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

False を出力します。これは、キャストによってボックス化された int 値の 2 つの個別のインスタンスへの参照が作成されるためです。

end の例

12.12.8 文字列等値演算子

定義済みの文字列等値演算子は次のとおりです。

bool operator ==(string x, string y);
bool operator !=(string x, string y);

次のいずれかが当てはまる場合、2 つの string 値が等しいと見なされます。

  • どちらの値も null
  • どちらの値も、各文字位置に同じ長さと同じ文字を持つ文字列インスタンスへのnull 以外の参照です。

文字列の等値演算子は、文字列参照ではなく文字列値を比較します。 2 つの個別の文字列インスタンスにまったく同じ文字シーケンスが含まれている場合、文字列の値は等しくなりますが、参照は異なります。

: §12.12.7 で説明されているように参照型の等値演算子を使用して、文字列値の代わりに文字列参照を比較できます。 end note

12.12.9 等値演算子を委任する

定義済みのデリゲート等値演算子は次のとおりです。

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

2 つのデリゲート インスタンスは、次のように等しいと見なされます。

  • いずれかのデリゲート インスタンスが nullされている場合、両方が nullされている場合にのみ等しくなります。
  • デリゲートの実行時の種類が異なる場合、それらは等しいことはありません。
  • 両方のデリゲート インスタンスに呼び出しリスト (§20.2) がある場合、それらのインスタンスは、呼び出しリストの長さが同じで、1 つの呼び出しリスト内の各エントリが、対応するエントリと (以下で定義されているように) 等しい場合にのみ、他の呼び出しリストの順序で等しくなります。

次の規則は、呼び出しリスト エントリの等価性を制御します。

  • 2 つの呼び出しリスト 項目が両方とも同じ静的メソッドを参照している場合、項目は等しくなります。
  • 2 つの呼び出しリスト エントリが両方とも同じターゲット オブジェクトで同じ非静的メソッドを参照している場合 (参照等値演算子で定義されている場合)、エントリは等しくなります。
  • キャプチャされた外部変数インスタンスの同じ (空の可能性がある) セットを持つ、セマンティックに同じ匿名関数 (§12.19) の評価から生成される呼び出しリスト エントリは、等しい (ただし必須ではありません) 許可されます。

演算子のオーバーロード解決がいずれかのデリゲート等値演算子に解決され、両方のオペランドのバインド時型が、System.Delegateではなく §20 で説明されているデリゲート型であり、バインド型のオペランド型間に ID 変換がない場合、バインド時エラーが発生します。

: この規則では、異なる種類のデリゲートのインスタンスへの参照であるため、null 以外の値を等しいと見なすことができる比較を禁止します。 end note

12.12.10 null 許容値型と null リテラル間の等値演算子

==演算子と!=演算子を使用すると、1 つのオペランドを null 許容値型の値にし、もう一方のオペランドをnullリテラルにすることができます(操作に対して定義済みまたはユーザー定義の演算子が存在しない場合でも)。

いずれかのフォームの操作の場合

x == null    null == x    x != null    null != x

ここで、xは null 許容値型の式です。演算子オーバーロードの解決 (§12.4.5) が該当する演算子を見つけられなかった場合、結果は代わりに xHasValue プロパティから計算されます。 具体的には、最初の 2 つのフォームは !x.HasValueに変換され、最後の 2 つのフォームは x.HasValueに変換されます。

12.12.11 タプル等値演算子

タプル等値演算子は、タプル オペランドの要素に構文順にペアワイズで適用されます。

==演算子または!=演算子の各オペランドxおよびyがタプルとして、またはタプル型 (§8.3.11) の値として分類される場合、演算子は tuple 等値演算子です

オペランド e がタプルとして分類される場合、 e1...en 要素はタプル式の要素式を評価した結果になります。 それ以外の場合、eがタプル型の値である場合、要素はt.Item1...t.Itemnte評価の結果になります。

タプル等値演算子のオペランド xy は同じアリティを持っているか、コンパイル時エラーが発生します。 xiyiの各要素のペアに対して、同じ等値演算子が適用され、型booldynamicboolへの暗黙的な変換を持つ型、またはtrue演算子とfalse演算子を定義する型の結果が得られます。

タプル等値演算子 x == y は、次のように評価されます。

  • 左側のオペランド x が評価されます。
  • 右側のオペランド y が評価されます。
  • 構文の順序で xi および yi される要素のペアごとに、次の手順を実行します。
    • 演算子 xi == yi が評価され、 bool 型の結果が次のように取得されます。
      • 比較によって bool が生成された場合、それが結果になります。
      • それ以外の場合、比較によって dynamic が生成された場合、演算子 false が動的に呼び出され、結果の bool 値は論理否定演算子 (!) で否定されます。
      • それ以外の場合、比較の型に boolへの暗黙的な変換がある場合、その変換が適用されます。
      • それ以外の場合、比較の型に演算子 falseがある場合、その演算子が呼び出され、結果の bool 値は論理否定演算子 (!) で否定されます。
    • 結果の boolfalse場合、それ以上の評価は行われず、タプル等値演算子の結果は false
  • すべての要素比較が true生成された場合、タプル等値演算子の結果は true

タプル等値演算子 x != y は、次のように評価されます。

  • 左側のオペランド x が評価されます。
  • 右側のオペランド y が評価されます。
  • 構文の順序で xi および yi される要素のペアごとに、次の手順を実行します。
    • 演算子 xi != yi が評価され、 bool 型の結果が次のように取得されます。
      • 比較によって bool が生成された場合、それが結果になります。
      • それ以外の場合、比較によって dynamic が生成された場合、演算子 true が動的に呼び出され、結果の bool 値が結果になります。
      • それ以外の場合、比較の型に boolへの暗黙的な変換がある場合、その変換が適用されます。
      • それ以外の場合、比較の型に演算子 trueがある場合、その演算子が呼び出され、結果の bool 値が結果になります。
    • 結果の booltrue場合、それ以上の評価は行われず、タプル等値演算子の結果は true
  • すべての要素比較が false生成された場合、タプル等値演算子の結果は false

12.12.12 Is 演算子

is演算子には 2 つの形式があります。 1 つは、右側に型を持つ is 型演算子です。 もう 1 つは、右側にパターンがある is-pattern 演算子です。

12.12.12.1 is-type 演算子

is-type 演算子は、オブジェクトの実行時の型が特定の型と互換性があるかどうかを確認するために使用されます。 チェックは実行時に実行されます。 Eが式で、Tdynamic以外の型である操作E is Tの結果は、Eが null 以外であり、参照変換、ボックス化変換、ボックス化解除変換、ラップ解除変換、またはラップ解除変換によって型Tに正常に変換できるかどうかを示すブール値です。

操作は次のように評価されます。

  1. Eが匿名関数またはメソッド グループの場合、コンパイル時エラーが発生します
  2. Enullリテラルの場合、またはEの値がnullの場合、結果はfalse
  3. それ以外:
  4. REのランタイム型にします。
  5. D次のようにRから派生します。
  6. Rが null 許容値型の場合、DRの基になる型です。
  7. それ以外の場合、DR です。
  8. 結果は、次のように DT によって異なります。
  9. Tが参照型の場合、結果は次の場合にtrueされます。
    • DTの間に ID 変換が存在する
    • D は参照型であり、 D から T への暗黙的な参照変換が存在します。
    • どちらか: D は値型であり、 D から T へのボックス化変換が存在します。
      または、 D は値型であり、 TDによって実装されるインターフェイス型です。
  10. Tが null 許容値型の場合、DTの基になる型である場合、結果はtrueされます。
  11. Tが null 非許容値型の場合、DTが同じ型の場合、結果はtrueされます。
  12. それ以外の場合、結果は false です。

ユーザー定義の変換は、 is 演算子では考慮されません。

: is 演算子は実行時に評価されるため、すべての型引数が置換されており、考慮すべきオープン型 (§8.4.3) はありません。 end note

: is 演算子は、コンパイル時の型と変換の観点から次のように理解できます。ここで、 CEのコンパイル時の型です。

  • eのコンパイル時の型がTと同じ場合、 または、暗黙的な参照変換 (§10.2.8)、ボックス化変換 (§10.2.9)、折り返し変換 (§§ 10.6)、または明示的なアンラップ変換 (§10.6) が、Eのコンパイル時の型からTに存在します。
    • Cが null 非許容値型の場合、操作の結果はtrue
    • それ以外の場合、操作の結果は E != nullの評価と同じです。
  • それ以外の場合、明示的な参照変換 (§10.3.5) またはボックス化解除変換 (§10.3.7) が C から Tに存在する場合、または C または T がオープン型 (§8.4.3) である場合は、上記のランタイム チェックが実行されます。
  • それ以外の場合、 E から型 T への参照、ボックス化、折り返し、またはラップ解除の変換はできず、操作の結果は false。 コンパイラは、コンパイル時の型に基づいて最適化を実装できます。

end note

12.12.12.2 is-pattern 演算子

is-pattern 演算子は、式によって計算された値が特定のパターン (§11matches) かどうかを確認するために使用されます。 チェックは実行時に実行されます。 値がパターンと一致する場合、is-pattern 演算子の結果は true になります。それ以外の場合は false です。

ET型の関係式であり、Pがパターンであるフォーム E is Pの式の場合、次のいずれかが保持されている場合はコンパイル時エラーになります。

  • E は値を指定しないか、型を持っていません。
  • パターン Pは、型Tに適用できません (§11.2)。

12.12.13 as 演算子

as演算子は、値を特定の参照型または null 許容値型に明示的に変換するために使用されます。 キャスト式 (§12.9.7) とは異なり、 as 演算子は例外をスローしません。 代わりに、指定された変換が不可能な場合、結果の値は null

フォーム E as Tの操作では、 E は式で、 T は参照型、参照型と呼ばれる型パラメーター、または null 許容値型である必要があります。 さらに、次のうち少なくとも 1 つが true であるか、それ以外の場合はコンパイル時エラーが発生します。

  • ID (§10.2.2)、暗黙的な null 許容 (§10.2.6)、暗黙的参照 (§10.2.8)、ボックス化 (§10.2.9) )、明示的な null 許容 (§10.3.4)、明示的な参照 (§10.3.5)、またはラップ (§8.3.12) 変換が E から Tに存在します。
  • EまたはTの型は開いている型です。
  • Enull リテラルです。

Eのコンパイル時の型がdynamicされていない場合、操作E as Tと同じ結果が生成されます

E is T ? (T)(E) : (T)null

ただし、E が評価されるのは 1 回だけです。 コンパイラは、上記の拡張によって示される 2 つのランタイム型チェックとは対照的に、最大 1 つのランタイム型チェックを実行するように E as T を最適化することが期待できます。

キャスト演算子とは異なり、 E のコンパイル時の型が dynamic場合、 as 演算子は動的にバインドされません (§12.3.3)。 したがって、この場合の拡張は次のとおりです。

E is T ? (T)(object)(E) : (T)null

ユーザー定義の変換など、一部の変換は as 演算子では実行できず、代わりにキャスト式を使用して実行する必要があることに注意してください。

: この例では

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

Gの型パラメーターTは、クラス制約があるため、参照型であることがわかっています。 ただし、Hの型パラメーターUは使用されないため、Hでのas演算子の使用は許可されません。

end の例

12.13 論理演算子

12.13.1 全般

&, ^演算子と|演算子は論理演算子と呼ばれます。

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

論理演算子のオペランドにコンパイル時の型 dynamicがある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamicされ、コンパイル時の型が dynamicされているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。

フォーム x «op» yの演算の場合、«op» は論理演算子の 1 つであり、オーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。

定義済みの論理演算子については、次のサブクラスで説明します。

12.13.2 整数論理演算子

定義済みの整数論理演算子は次のとおりです。

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

&演算子は、2 つのオペランドのビットごとの論理 AND を計算し、|演算子は 2 つのオペランドのビットごとの論理 OR を計算し、^演算子は 2 つのオペランドのビットごとの論理排他的 OR を計算します。 これらの操作からのオーバーフローは発生しません。

上記で定義した、リフトされていない定義済みの整数論理演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.13.3 列挙論理演算子

すべての列挙型 E は、次の定義済みの論理演算子を暗黙的に提供します。

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

x «op» yを評価した結果(xyは基になる型Uを持つ列挙型Eの式であり、«op» は論理演算子の 1 つであり、(E)((U)x «op» (U)y)の評価とまったく同じです。 つまり、列挙型の論理演算子は、2 つのオペランドの基になる型に対して論理演算を実行するだけです。

上記で定義した、リフトされていない定義済み列挙論理演算子のリフト (§12.4.8) フォームも事前に定義されています。

12.13.4 ブール論理演算子

定義済みのブール論理演算子は次のとおりです。

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

xy の両方が true であれば、x & y の結果は true です。 それ以外の場合、結果は false です。

xまたはytrueされている場合、x | yの結果はtrueされます。 それ以外の場合、結果は false です。

x ^ yの結果は、xtrueされ、yfalseされている場合、またはxfalseされ、ytrue場合にtrueされます。 それ以外の場合、結果は false です。 オペランドが bool型の場合、 ^ 演算子は != 演算子と同じ結果を計算します。

12.13.5 Null 許容ブール値 & |演算子

null 許容ブール型 bool? は、 truefalse、および nullの 3 つの値を表すことができます。

他の二項演算子と同様に、論理演算子の &| のリフト形式 (§12.13.4) も事前に定義されています。

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

リフトされた & 演算子と | 演算子のセマンティクスは、次の表で定義します。

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

: bool? 型は概念的には、SQL のブール式に使用される 3 つの値を持つ型に似ています。 上の表は SQL と同じセマンティクスに従います。一方、§12.4.8 の規則を&および|演算子には適用しません。 §12.4.8 の規則ではリフトされた^演算子に SQL に似たセマンティクスが既に提供されています。 end note

12.14 条件付き論理演算子

12.14.1 全般

&& 演算子と || 演算子は、条件付き論理演算子と呼ばれます。 これらは、"短絡" 論理演算子とも呼ばれます。

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

&&演算子と||演算子は、&演算子と|演算子の条件付きバージョンです。

  • 操作 x && y は操作 x & yに対応します。ただし、 yxfalseされていない場合にのみ評価されます。
  • 操作 x || y は操作 x | yに対応します。ただし、 yxtrueされていない場合にのみ評価されます。

: ショート サーキットで "not true" 条件と "not false" 条件が使用される理由は、ユーザー定義の条件付き演算子でショートサーキットが適用されるタイミングを定義できるようにするためです。 ユーザー定義型は、 operator truefalse を返し、 operator falsefalseを返す状態になる可能性があります。 そのような場合、 &&|| もショートしません。 end note

条件論理演算子のオペランドにコンパイル時の型 dynamicがある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamicされ、コンパイル時の型が dynamicされているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。

フォーム x && yまたはx || yの操作は、操作がx & yまたはx | y書き込まれたかのようにオーバーロード解決 (§12.4.5) を適用することによって処理されます。 このとき、次のようになります。

  • オーバーロードの解決で 1 つの最適な演算子が見つからない場合、またはオーバーロード解決で定義済みの整数論理演算子または null 許容ブール論理演算子 (§12.13.5) のいずれかを選択すると、バインド時エラーが発生します。
  • それ以外の場合、選択した演算子が定義済みのブール論理演算子 (§12.13.4) の 1 つである場合、操作は §12.14.2 で説明されているように処理されます。
  • それ以外の場合、選択した演算子はユーザー定義演算子であり、操作は §12.14.3 で説明されているように処理されます。

条件付き論理演算子を直接オーバーロードすることはできません。 ただし、条件付き論理演算子は通常の論理演算子の観点から評価されるため、通常の論理演算子のオーバーロードは、特定の制限があり、条件付き論理演算子のオーバーロードとも見なされます。 これについては、 §12.14.3 で詳しく説明します。

12.14.2 ブール条件論理演算子

&&または||のオペランドがbool型の場合、またはオペランドが該当するoperator &またはoperator |を定義せず、boolへの暗黙的な変換を定義する型の場合、演算は次のように処理されます。

  • 操作 x && yx ? y : falseとして評価されます。 つまり、 x は最初に評価され、 bool型に変換されます。 次に、 xtrueされると、 y が評価され、型 boolに変換され、これが操作の結果になります。 それ以外の場合、操作の結果は false
  • 操作 x || yx ? true : yとして評価されます。 つまり、 x は最初に評価され、 bool型に変換されます。 次に、 xtrue場合、操作の結果は true。 それ以外の場合、 y は評価され、 bool型に変換され、これが操作の結果になります。

12.14.3 ユーザー定義の条件付き論理演算子

&&または||のオペランドが、適用可能なユーザー定義のoperator &またはoperator |を宣言する型である場合、次の両方が true になります。ここで、Tは、選択した演算子が宣言されている型です。

  • 選択した演算子の各パラメーターの戻り値の型と型は T。 つまり、演算子は、 T型の 2 つのオペランドの論理 AND または論理 OR を計算し、 T型の結果を返します。
  • T は、 operator trueoperator falseの宣言を含む必要があります。

これらの要件のいずれかが満たされていない場合、バインド時エラーが発生します。 それ以外の場合、 && または || 操作は、ユーザー定義の operator true または operator false を選択したユーザー定義演算子と組み合わせることによって評価されます。

  • 操作x && yT.false(x) ? x : T.&(x, y)として評価されます。ここで、T.false(x)Tで宣言されたoperator falseの呼び出しであり、T.&(x, y)は選択したoperator &の呼び出しです。 つまり、 x が最初に評価され、結果に対して operator false が呼び出され、 x が確実に false であるかどうかを判断します。 次に、 x が確実に false の場合、操作の結果は、 xに対して以前に計算された値になります。 それ以外の場合は、 y が評価され、選択した operator & が、 x に対して以前に計算された値と、操作の結果を生成するために y のために計算された値に対して呼び出されます。
  • 操作x || yT.true(x) ? x : T.|(x, y)として評価されます。ここで、T.true(x)Tで宣言されたoperator trueの呼び出しであり、T.|(x, y)は選択したoperator |の呼び出しです。 つまり、 x が最初に評価され、結果に対して operator true が呼び出され、 x が確実に当てはまるかどうかを判断します。 次に、 x が確実に true の場合、操作の結果は、 xに対して以前に計算された値になります。 それ以外の場合は、 y が評価され、選択した operator | が、 x に対して以前に計算された値と、操作の結果を生成するために y のために計算された値に対して呼び出されます。

いずれの操作でも、 x によって指定された式は 1 回だけ評価され、 y によって指定された式は評価されず、1 回だけ評価もされません。

12.15 null 合体演算子

??演算子は、null 合体演算子と呼ばれます。

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

フォーム a ?? bの null 結合式では、 anullでない場合、結果は aされます。それ以外の場合、結果は b。 操作は、anullされている場合にのみ、bを評価します。

null 合体演算子は右連想です。つまり、操作は右から左にグループ化されます。

: フォーム a ?? b ?? c の式は、 ?? (b ?? c)として評価されます。 一般に、フォーム E1 ?? E2 ?? ... ?? ENの式は、nullでないオペランドの最初の式を返します。また、すべてのオペランドがnullされている場合はnullします。 end の例

a ?? b式の型は、オペランドで使用できる暗黙的な変換によって異なります。 優先順に、 a ?? bの型はA₀A、またはBです aAaの型 (型を持つ場合)、Bbの型 (bに型がある場合)、A₀は null 許容値型の場合は基になるAの型A。それ以外の場合はA。 具体的には、 a ?? b は次のように処理されます。

  • Aが存在し、null 許容値型または参照型でない場合は、コンパイル時エラーが発生します。
  • それ以外の場合、 A が存在し、 b が動的な式である場合、結果の型は dynamic。 実行時に、 a が最初に評価されます。 anullされていない場合、adynamicに変換され、これが結果になります。 それ以外の場合、 b が評価され、これが結果になります。
  • それ以外の場合、 A が存在し、null 許容値型であり、 b から A₀への暗黙的な変換が存在する場合、結果の型は A₀。 実行時に、 a が最初に評価されます。 anullされていない場合、aA₀型にラップ解除され、結果になります。 それ以外の場合は、 b が評価され、 A₀型に変換され、これが結果になります。
  • それ以外の場合、 A が存在し、 b から Aへの暗黙的な変換が存在する場合、結果の型は A。 実行時に、a が最初に評価されます。 a が null でない場合、a は結果になります。 それ以外の場合は、 b が評価され、 A型に変換され、これが結果になります。
  • それ以外の場合、 A が存在し、null 許容値型である場合、 b は型 B を持ち、 A₀ から Bへの暗黙的な変換が存在する場合、結果の型は B。 実行時に、 a が最初に評価されます。 anullされていない場合、aA₀型にラップ解除され、B型に変換され、これが結果になります。 それ以外の場合、 b が評価され、結果になります。
  • それ以外の場合、 b に型 B があり、 a から Bへの暗黙的な変換が存在する場合、結果の型は B。 実行時に、 a が最初に評価されます。 anullされていない場合、aは型Bに変換され、これが結果になります。 それ以外の場合、 b が評価され、結果になります。

それ以外の場合は、 ab に互換性がありません。 a コンパイル時エラーが発生します。

12.16 throw 式演算子

throw_expression
    : 'throw' null_coalescing_expression
    ;

throw_expressionは、null_coalescing_expressionを評価することによって生成された値をスローします。 式は暗黙的に System.Exceptionに変換でき、式を評価した結果はスローされる前に System.Exception に変換されます。 throw 式の評価実行時の動作は、throw ステートメント (§13.10.6) に指定されているものと同じです。

throw_expressionには型がありません。 throw_expressionは、単純スロー変換によってすべての型に変換できます

throw 式は、次の構文コンテキストでのみ発生します。

  • 三項条件演算子 (?:) の 2 番目または 3 番目のオペランドとして。
  • null 合体演算子の 2 番目のオペランド (??)。
  • 式形式のラムダまたはメンバーの本体。

12.17 宣言式

宣言式は、ローカル変数を宣言します。

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

simple_name _は、単純な名前検索で関連付けられた宣言が見つからなかった場合にも宣言式と見なされます (§12.8.4)。 宣言式として使用する場合、 _simple 破棄と呼ばれます。 意味的には var _に相当しますが、より多くの場所で許可されます。

宣言式は、次の構文コンテキストでのみ発生します。

  • argument_listout argument_valueとして。
  • 単純な破棄 _ として、単純な割り当ての左側 (§12.21.2) で構成されます。
  • 1 つ以上の再帰的に入れ子になったtuple_expressiontuple_elementとして、最も外側は分解割り当ての左側で構成されます。 deconstruction_expressionは、宣言式が構文的に存在しない場合でも、この位置で宣言式を生み出します。

: これは、宣言式をかっこで分けることができないことを意味します。 end note

declaration_expressionで宣言された暗黙的に型指定された変数が、宣言されているargument_list内で参照されるのはエラーです。

これは、 declaration_expression で宣言された変数が、それが発生する分解割り当て内で参照されるエラーです。

単純な破棄、またはlocal_variable_type単純に型指定された変数として分類var識別子である宣言式。 式には型がなく、ローカル変数の型は次のように構文コンテキストに基づいて推論されます。

  • argument_listでは、変数の推論された型は、対応するパラメーターの宣言された型です。
  • 単純な代入の左側として、変数の推論された型は代入の右側の型です。
  • 単純な代入の左側の tuple_expression では、変数の推論された型は、代入の右側 (分解後) の対応するタプル要素の型です。

それ以外の場合、宣言式は 明示的に型指定された変数 変数として分類され、式の型と宣言された変数は、 local_variable_typeによって指定されるものとします。

識別子 _ を持つ宣言式は破棄 (§9.2.9.1) であり、変数の名前は導入されません。 _以外の識別子を持つ宣言式は、その名前を最も近い外側のローカル変数宣言空間 (§7.3) に導入します。

例:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

s1の宣言は、明示的に型指定された宣言式と暗黙的に型指定された宣言式の両方を示します。 推論されたb1の型は、M1の対応する出力パラメーターの型であるため、boolされます。 後続の WriteLine は、外側のスコープに導入された i1b1にアクセスできます。

s2の宣言は、i2が宣言された引数リスト内で参照が行われるため、Mへの入れ子になった呼び出しでi2を使用しようとしましたが、これは許可されていません。 一方、最後の引数の b2 への参照は許可されます。これは、 b2 が宣言された入れ子になった引数リストの末尾の後に発生するためです。

s3の宣言は、破棄される暗黙的に型指定された宣言式と明示的に型指定された宣言式の両方の使用を示しています。 破棄では名前付き変数が宣言されないため、識別子 _ の複数回の出現が許可されます。

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

この例では、変数と破棄の両方に対して暗黙的および明示的に型指定された宣言式を分解代入で使用する方法を示します。 simple_name _は、_の宣言が見つからない場合のvar _に相当します。

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

この例では、外側のスコープで変数を指定するため、_が使用できない場合に暗黙的に型指定された破棄を提供するvar _の使用を示します。

end の例

12.18 条件演算子

?:演算子は条件付き演算子と呼ばれます。 時には三項演算子とも呼ばれます。

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

refが存在する場合、条件演算子では throw 式 (§12.16) は使用できません。

フォーム b ? x : y の条件式は、最初に条件 bを評価します。 次に、 btrueされると、 x が評価され、操作の結果になります。 それ以外の場合、 y が評価され、操作の結果になります。 条件式では、 xyの両方が評価されることはありません。

条件演算子は右連想です。つまり、操作は右から左にグループ化されます。

: a ? b : c ? d : e 形式の式は、 a ? b : (c ? d : e)として評価されます。 end の例

?:演算子の最初のオペランドは、暗黙的にboolに変換できる式、またはoperator trueを実装する型の式である必要があります。 どちらの要件も満たされていない場合は、コンパイル時エラーが発生します。

refが存在する場合:

  • ID 変換は、2 つの variable_referenceの型の間に存在するものとし、結果の型はどちらの型でもかまいません。 いずれかの型が dynamicの場合、型推論は dynamic を優先します (§8.7)。 いずれかの型がタプル型 (§8.3.11) の場合、同じ序数位置の要素名が両方のタプルで一致する場合、型推論には要素名が含まれます。
  • 結果は変数参照であり、両方の variable_referenceが書き込み可能な場合は書き込み可能です。

: ref が存在する場合、 conditional_expression は変数参照を返します。これは、 = ref 演算子を使用して参照変数に割り当てたり、参照/入力/出力パラメーターとして渡したりできます。 end note

refが存在しない場合、?:演算子の 2 番目と 3 番目のオペランド (xy) は、条件式の型を制御します。

  • xに型Xがあり、yに型Yがある場合は、
    • XYの間に ID 変換が存在する場合、結果は一連の式の最も一般的な型になります (§12.6.3.15)。 いずれかの型が dynamicの場合、型推論は dynamic を優先します (§8.7)。 いずれかの型がタプル型 (§8.3.11) の場合、同じ序数位置の要素名が両方のタプルで一致する場合、型推論には要素名が含まれます。
    • それ以外の場合、暗黙的な変換 (§10.2) が X から Yに存在するが、 Y から Xには存在しない場合、 Y は条件式の型になります。
    • それ以外の場合、暗黙的な列挙変換 (§10.2.4) が X から Yに存在する場合、 Y は条件式の型になります。
    • それ以外の場合、暗黙的な列挙変換 (§10.2.4) が Y から Xに存在する場合、 X は条件式の型になります。
    • それ以外の場合、暗黙的な変換 (§10.2) が Y から Xに存在するが、 X から Yには存在しない場合、 X は条件式の型になります。
    • それ以外の場合は、式の型を特定できなくなり、コンパイル時エラーが発生します。
  • xyの 1 つだけに型があり、xyの両方がその型に暗黙的に変換できる場合、それが条件式の型になります。
  • それ以外の場合は、式の型を特定できなくなり、コンパイル時エラーが発生します。

フォーム b ? ref x : ref y の ref 条件式の実行時処理は、次の手順で構成されます。

  • まず、bが評価され、bbool値が決定されます。
    • bの型からboolへの暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、bool値が生成されます。
    • それ以外の場合は、bの型によって定義されたoperator trueが呼び出され、bool値が生成されます。
  • 上記の手順で生成された bool 値が true場合、 x が評価され、結果の変数参照が条件式の結果になります。
  • それ以外の場合、 y が評価され、結果の変数参照が条件式の結果になります。

フォーム b ? x : y の条件式の実行時処理は、次の手順で構成されます。

  • まず、bが評価され、bbool値が決定されます。
    • bの型からboolへの暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、bool値が生成されます。
    • それ以外の場合は、bの型によって定義されたoperator trueが呼び出され、bool値が生成されます。
  • 上記の手順で生成された bool 値が true場合、 x が評価され、条件式の型に変換され、これが条件式の結果になります。
  • それ以外の場合、 y は評価され、条件式の型に変換され、これが条件式の結果になります。

12.19 匿名関数式

12.19.1 全般

不要な関数は、"インライン" メソッド定義を表す式です。 匿名関数自体には値や型はありませんが、互換性のあるデリゲート型または式ツリー型に変換できます。 匿名関数変換の評価は、変換のターゲット型によって異なります。デリゲート型の場合、匿名関数が定義するメソッドを参照するデリゲート値に変換が評価されます。 式ツリー型の場合、変換はメソッドの構造をオブジェクト構造として表す式ツリーに評価されます。

: 歴史的な理由から、匿名関数には、 lambda_expressionanonymous_method_expressionという 2 種類の構文があります。 ほぼすべての目的で、 lambda_expressionanonymous_method_expressionよりも簡潔で表現力が高く、下位互換性のために言語に残っています。 end note

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

null_conditional_invocation_expression式の両方が適用される場合にanonymous_function_bodyを認識する場合前者を選択する必要があります。

: ここでの代替手段の重複と優先順位は、説明上の便宜上のみであり、文法規則を詳しく説明して重複を取り除く場合があります。 ANTLR やその他の文法システムでは、同じ利便性が採用されているため、 anonymous_function_body には指定されたセマンティクスが自動的に適用されます。 end note

: として扱われる場合、Mの結果の型がvoid (§12.8.13) の場合、x?.M()などの構文形式はエラーになります。 ただし、 null_conditional_invocation_expressionとして扱われる場合、結果の型は voidできます。 end note

: List<T>.Reverse の結果の型が void。 次のコードでは、匿名式の本文は null_conditional_invocation_expressionであるため、エラーではありません。

Action<List<int>> a = x => x?.Reverse();

end の例

=>演算子は代入 (=) と同じ優先順位を持ち、右連想です。

async修飾子を持つ匿名関数は非同期関数であり、§15.15 で説明されている規則に従います。

lambda_expressionの形式の匿名関数のパラメーターは、明示的または暗黙的に型指定できます。 明示的に型指定されたパラメーター リストでは、各パラメーターの型が明示的に指定されます。 暗黙的に型指定されたパラメーター リストでは、匿名関数が発生するコンテキストからパラメーターの型が推論されます。具体的には、匿名関数が互換性のあるデリゲート型または式ツリー型に変換されると、その型はパラメーター型 (§10.7) を提供します。

暗黙的に型指定された単一のパラメーターを持つ lambda_expression では、パラメーター リストからかっこを省略できます。 つまり、フォームの匿名関数です。

( «param» ) => «expr»

を省略できます。

«param» => «expr»

anonymous_method_expressionの形式の匿名関数のパラメーター リストは省略可能です。 指定した場合、パラメーターは明示的に型指定されます。 そうでない場合、匿名関数は、出力パラメーターを含まないパラメーター リストを持つデリゲートに変換できます。

匿名関数の ブロック 本体は常に到達可能です (§13.2)。

: 匿名関数の例をいくつか次に示します。

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

end の例

lambda_expressionanonymous_method_expressionの動作は、次の点を除いて同じです。

  • anonymous_method_expressionパラメーター リストを完全に省略すると、値パラメーターの任意のリストのデリゲート型への変換が可能になります。
  • lambda_expressionではパラメーター型を省略して推論できますが、 anonymous_method_expressionではパラメーター型を明示的に指定する必要があります。
  • lambda_expressionの本体は式またはブロックにすることができますが、anonymous_method_expressionの本体はブロックになります。
  • 互換性のある式ツリー型 (§8.6) に変換できるのは、lambda_expressionだけです。

12.19.2 匿名関数シグネチャ

匿名関数の anonymous_function_signature は、名前と、必要に応じて匿名関数のパラメーターの型を定義します。 匿名関数のパラメーターのスコープは、 anonymous_function_body (§7.7) です。 パラメーター リスト (指定されている場合) と共に、anonymous-method-body は宣言空間 (§7.3) を構成します。 したがって、匿名関数のパラメーターの名前が、 anonymous_method_expression または lambda_expressionを含むスコープを持つローカル変数、ローカル定数、またはパラメーターの名前と一致するコンパイル時エラーになります。

匿名関数に explicit_anonymous_function_signatureがある場合、互換性のあるデリゲート型と式ツリー型のセットは、同じ順序で同じパラメーター型と修飾子を持つものに制限されます (§10.7)。 メソッド グループ変換 (§10.8) とは対照的に、匿名関数パラメーター型の反分散はサポートされていません。 匿名関数に anonymous_function_signatureがない場合、互換性のあるデリゲート型と式ツリー型のセットは、出力パラメーターのない型に制限されます。

anonymous_function_signatureに属性やパラメーター配列を含めることはできません。 ただし、 anonymous_function_signature は、パラメーター リストにパラメーター配列が含まれるデリゲート型と互換性がある場合があります。

また、互換性がある場合でも、式ツリー型への変換はコンパイル時に失敗する可能性があることに注意してください (§8.6)。

12.19.3 匿名関数本体

匿名関数の本体 (expression または block) には、次の規則が適用されます。

  • 匿名関数にシグネチャが含まれている場合は、シグネチャで指定されたパラメーターを本文で使用できます。 匿名関数にシグネチャがない場合は、パラメーター (§10.7) を持つデリゲート型または式型に変換できますが、本文ではパラメーターにアクセスできません。
  • 最も近い外側の匿名関数のシグネチャ (存在する場合) で指定された参照渡しパラメーターを除き、本文が参照渡しパラメーターにアクセスするのはコンパイル時エラーです。
  • 最も近い外側の匿名関数のシグネチャで指定されたパラメーター (存在する場合) を除き、本文が ref struct 型のパラメーターにアクセスするのはコンパイル時エラーです。
  • thisの型が構造体型の場合、本文がthisにアクセスするためのコンパイル時エラーです。 これは、アクセスが明示的 (this.xと同様) か暗黙的か (xxが構造体のインスタンス メンバーである場合) に当てはまります。 この規則は、このようなアクセスを禁止するだけで、メンバールックアップが構造体のメンバーに結果を与えるかどうかには影響しません。
  • 本体は、匿名関数の外部変数 (§12.19.6) にアクセスできます。 外部変数へのアクセスは、 lambda_expression または anonymous_method_expression が評価されるときにアクティブな変数のインスタンスを参照します (§12.19.7)。
  • 本文に、 goto ステートメント、 break ステートメント、またはターゲットが本体の外部にあるか、含まれている匿名関数の本体内にある continue ステートメントが含まれているのは、コンパイル時エラーです。
  • 本体の return ステートメントは、外側の関数メンバーからではなく、最も近い外側の匿名関数の呼び出しから制御を返します。

lambda_expressionまたはanonymous_method_expressionの評価と呼び出し以外に匿名関数のブロックを実行する方法があるかどうかは明示的に指定されていません。 特に、コンパイラは、1 つ以上の名前付きメソッドまたは型を合成して匿名関数を実装することを選択できます。 このような合成された要素の名前は、コンパイラ用に予約された形式である必要があります (§6.4.3)。

12.19.4 オーバーロードの解決

引数リスト内の匿名関数は、型の推論とオーバーロードの解決に関与します。 正確な規則については、 §12.6.3 および §12.6.4 を参照してください。

: 次の例は、オーバーロードの解決に対する匿名関数の効果を示しています。

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

ItemList<T> クラスには、2 つのSumメソッドがあります。 それぞれが selector 引数を受け取り、リスト アイテムから合計する値を抽出します。 抽出される値は、 int または double のいずれかであり、結果の合計も同様に、 int または doubleのいずれかになります。

たとえば、 Sum メソッドを使用して、詳細行の一覧から注文の合計を計算できます。

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

orderDetails.Sumの最初の呼び出しでは、匿名関数d => d.UnitCountFunc<Detail,int>Func<Detail,double>の両方と互換性があるため、両方のSumメソッドが適用されます。 ただし、オーバーロードの解決では、Func<Detail,int>への変換がFunc<Detail,double>への変換よりも優れているため、最初のSumメソッドを選択します。

orderDetails.Sumの 2 番目の呼び出しでは、匿名関数d => d.UnitPrice * d.UnitCountdouble型の値を生成するため、2 番目のSum メソッドのみが適用されます。 したがって、オーバーロード解決は、その呼び出しの 2 番目の Sum メソッドを選択します。

end の例

12.19.5 匿名関数と動的バインディング

匿名関数は、動的にバインドされた演算の受信者、引数、またはオペランドにすることはできません。

12.19.6 外部変数

12.19.6.1 全般

スコープに lambda_expression または anonymous_method_expression が含まれるローカル変数、値パラメーター、またはパラメーター配列は、匿名関数の outer 変数 と呼ばれます。 クラスのインスタンス関数メンバーでは、この値は値パラメーターと見なされ、関数メンバーに含まれるすべての匿名関数の外部変数です。

12.19.6.2 キャプチャされた外部変数

外部変数が匿名関数によって参照されている場合、外部変数は匿名関数によって キャプチャ されていると言われます。 通常、ローカル変数の有効期間は、関連付けられているブロックまたはステートメントの実行に制限されます (§9.2.9)。 ただし、キャプチャされた外部変数の有効期間は、少なくとも匿名関数から作成されたデリゲートまたは式ツリーがガベージ コレクションの対象になるまで延長されます。

: この例では

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

ローカル変数 x は匿名関数によってキャプチャされ、 x の有効期間は、少なくとも F から返されたデリゲートがガベージ コレクションの対象になるまで拡張されます。 匿名関数の各呼び出しは、 xの同じインスタンスで動作するため、例の出力は次のようになります。

1
2
3

end の例

ローカル変数または値パラメーターが匿名関数によってキャプチャされる場合、ローカル変数またはパラメーターは固定変数 (§23.4) と見なされなくなりますが、代わりに移動可能変数と見なされます。 ただし、キャプチャされた外部変数は fixed ステートメント (§23.7) では使用できないため、キャプチャされた外部変数のアドレスを取得できません。

: キャプチャされていない変数とは異なり、キャプチャされたローカル変数を複数の実行スレッドに同時に公開できます。 end note

12.19.6.3 ローカル変数のインスタンス化

ローカル変数は、実行が変数のスコープに入るとインスタンス化されていると見なされます。

: たとえば、次のメソッドが呼び出されると、 x ローカル変数がインスタンス化され、ループの反復処理ごとに 1 回、3 回初期化されます。

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

ただし、 x の宣言をループの外側に移動すると、 xが 1 回インスタンス化されます。

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

end の例

キャプチャされない場合は、ローカル変数がインスタンス化される頻度を正確に確認する方法はありません。インスタンス化の有効期間が不整合であるため、各インスタント化で同じストレージの場所を単に使用できます。 ただし、匿名関数がローカル変数をキャプチャすると、インスタンス化の効果が明らかになります。

: 例

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

この例では、次のように出力されます。

1
3
5

ただし、 x の宣言がループの外側に移動された場合:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

出力は次のとおりです。

5
5
5

コンパイラは、3 つのインスタンス化を単一のデリゲート インスタンス (§10.7.2) に最適化することが許可されていることに注意してください (ただし、必須ではありません)。

end の例

for ループで反復変数が宣言されている場合、その変数自体はループの外部で宣言されていると見なされます。

: 反復変数自体をキャプチャするように例が変更された場合:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

反復変数のインスタンスが 1 つだけキャプチャされ、出力が生成されます。

3
3
3

end の例

匿名関数デリゲートは、キャプチャされた変数の一部を共有しても、別のインスタンスを持つことができます。

: たとえば、 F が次の値に変更された場合

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

3 つのデリゲートは、 x の同じインスタンスをキャプチャしますが、 yの個別のインスタンスをキャプチャします。出力は次のとおりです。

1 1
2 1
3 1

end の例

個別の匿名関数は、外部変数の同じインスタンスをキャプチャできます。

: この例では次のようになります。

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

2 つの匿名関数は、ローカル変数 xの同じインスタンスをキャプチャするため、その変数を介して "通信" できます。 この例の出力は次のとおりです。

5
10

end の例

12.19.7 匿名関数式の評価

匿名関数 F は、常にデリゲート型 D または式ツリー型 Eに、直接またはデリゲート作成式 new D(F)の実行によって変換されます。 この変換は、 §10.7 で説明されているように、匿名関数の結果を決定します。

12.19.8 実装例

このサブクラスは有益です。

このサブクラウズでは、他の C# コンストラクトの観点から匿名関数変換を実装する可能性について説明します。 ここで説明する実装は、商用 C# コンパイラで使用されるのと同じ原則に基づいていますが、決して必須の実装ではなく、可能な唯一の実装でもありません。 式ツリーへの変換については、厳密なセマンティクスがこの仕様の範囲外であるため、簡単に説明するだけです。

このサブドキュメントの残りの部分では、異なる特性を持つ匿名関数を含むコードの例をいくつか示します。 各例では、他の C# コンストラクトのみを使用するコードへの対応する翻訳が提供されます。 この例では、識別子 D は、次のデリゲート型を表していると見なされます。

public delegate void D();

匿名関数の最も簡単な形式は、外部変数をキャプチャしない関数です。

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

これは、匿名関数のコードが配置されるコンパイラによって生成された静的メソッドを参照するデリゲートインスタンス化に変換できます。

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

次の例では、匿名関数は thisのインスタンス メンバーを参照します。

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

これは、匿名関数のコードを含むコンパイラ生成インスタンス メソッドに変換できます。

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

この例では、匿名関数はローカル変数をキャプチャします。

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

ローカル変数の有効期間を、少なくとも匿名関数デリゲートの有効期間に拡張する必要があります。 これは、ローカル変数をコンパイラによって生成されたクラスのフィールドに "ホイスト" することによって実現できます。 ローカル変数 (§12.19.6.3) のインスタンス化は、コンパイラによって生成されたクラスのインスタンスの作成に対応し、ローカル変数へのアクセスは、コンパイラによって生成されたクラスのインスタンス内のフィールドへのアクセスに対応します。 さらに、匿名関数は、コンパイラによって生成されたクラスのインスタンス メソッドになります。

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

最後に、次の匿名関数は、 this と、有効期間が異なる 2 つのローカル変数をキャプチャします。

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

ここでは、異なるブロック内のローカルが独立した有効期間を持つことができるように、ローカルがキャプチャされるブロックごとにコンパイラによって生成されたクラスが作成されます。 内部ブロックのコンパイラによって生成されたクラスである __Locals2のインスタンスには、ローカル変数の z と、 __Locals1のインスタンスを参照するフィールドが含まれます。 外部ブロックのコンパイラによって生成されたクラスである __Locals1のインスタンスには、ローカル変数の y と、外側の関数メンバーの this を参照するフィールドが含まれています。 これらのデータ構造を使用すると、 __Local2のインスタンスを介してキャプチャされたすべての外部変数に到達することができ、匿名関数のコードをそのクラスのインスタンス メソッドとして実装できます。

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

ローカル変数をキャプチャするためにここで適用するのと同じ手法は、匿名関数を式ツリーに変換するときにも使用できます。コンパイラによって生成されたオブジェクトへの参照は式ツリーに格納でき、ローカル変数へのアクセスは、これらのオブジェクトに対するフィールド アクセスとして表すことができます。 この方法の利点は、デリゲートと式ツリーの間で "リフトされた" ローカル変数を共有できる点です。

情報テキストの末尾。

12.20 クエリ式

12.20.1 全般

クエリ式 は、SQL や XQuery などのリレーショナルおよび階層型クエリ言語に似たクエリの言語統合構文を提供します。

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

クエリ式は、 from 句で始まり、 select 句または group 句で終わります。 最初の from 句の後に、0 個以上の fromletwherejoin 、または orderby 句を指定できます。 各from句は、シーケンスの要素に対して範囲を指定する範囲変数を導入するジェネレーターです。 各 let 句には、前の範囲変数を使用して計算された値を表す範囲変数が導入されています。 各 where 句は、結果から項目を除外するフィルターです。 各 join 句は、ソース シーケンスの指定されたキーを別のシーケンスのキーと比較し、一致するペアを生成します。 各 orderby 句は、指定した条件に従って項目を並べ替えます。最後の select 句または group 句は、範囲変数の観点から結果の形状を指定します。 最後に、1 つのクエリの結果を後続のクエリのジェネレーターとして扱うことで、 into 句を使用してクエリを "スプライス" できます。

12.20.2 クエリ式のあいまいさ

クエリ式では、さまざまなコンテキスト キーワード (§6.4.4): ascendingbydescendingequalsfrom groupintojoinletonorderbyselectwhere

これらの識別子をキーワードと単純名の両方として使用することによって生じる可能性のあるあいまいさを回避するために、これらの識別子は、"@" (§6.4.4) でプレフィックスが付いている場合を除き、クエリ式内の任意の場所でキーワードと見なされます。その場合は、識別子と見なされます。 このため、クエリ式は、"from identifier" で始まり、その後に ";"、"="、"," を除くトークンが続く任意の式です。

12.20.3 クエリ式の変換

12.20.3.1 全般

C# 言語では、クエリ式の実行セマンティクスは指定されません。 クエリ式は、クエリ式パターン (§12.20.4) に準拠するメソッドの呼び出しに変換されます。 具体的には、クエリ式は、 WhereSelectSelectManyJoinGroupJoinOrderByOrderByDescendingThenByThenByDescendingGroupBy、および Castという名前のメソッドの呼び出しに変換されます。 これらのメソッドには、 §12.20.4 で説明されているように、特定のシグネチャと戻り値の型必要があります。 これらのメソッドは、クエリ対象のオブジェクトのインスタンス メソッド、またはオブジェクトの外部にある拡張メソッドである場合があります。 これらのメソッドは、クエリの実際の実行を実装します。

クエリ式からメソッド呼び出しへの変換は、型バインディングまたはオーバーロードの解決が実行される前に発生する構文マッピングです。 クエリ式の翻訳後、結果のメソッド呼び出しは通常のメソッド呼び出しとして処理され、コンパイル時エラーが明らかにされる可能性があります。 これらのエラー条件には、存在しないメソッド、間違った型の引数、型推論が失敗するジェネリック メソッドが含まれますが、これらに限定されません。

クエリ式は、さらなる削減が不可能になるまで、次の翻訳を繰り返し適用することによって処理されます。 翻訳はアプリケーションの順序で一覧表示されます。各セクションでは、前のセクションの翻訳が完全に実行されていることを前提としており、使い果たされると、同じクエリ式の処理でセクションが後で再検討されることはありません。

範囲変数への代入を含めるクエリ式のコンパイル時エラーです。または、参照または出力パラメーターの引数として範囲変数を使用します。

一部の翻訳では、 *で示される一連の識別子を持つ範囲変数が挿入されます。 詳細については、 §12.20.3.8 で説明します。

12.20.3.2 継続を含むクエリ式

クエリ本文に続く継続を含むクエリ式

from «x1» in «e1» «b1» into «x2» «b2»

に変換されます。

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

次のセクションの翻訳では、クエリに継続がないことを前提としています。

: 例:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

は次のように変換されます。

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

最後の翻訳は次のとおりです。

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

end の例

12.20.3.3 明示的な範囲変数型

範囲変数型を明示的に指定する from

from «T» «x» in «e»

に変換されます。

from «x» in ( «e» ) . Cast < «T» > ( )

範囲変数型を明示的に指定する join

join «T» «x» in «e» on «k1» equals «k2»

に変換されます。

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

次のセクションの翻訳では、クエリに明示的な範囲変数型がないことを前提としています。

: 例

from Customer c in customers
where c.City == "London"
select c

に変換されます。

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

最後の翻訳は次の内容です。

customers.
Cast<Customer>().
Where(c => c.City == "London")

end の例

: 明示的な範囲変数型は、非ジェネリック IEnumerable インターフェイスを実装するコレクションに対してクエリを実行する場合に便利ですが、ジェネリック IEnumerable<T> インターフェイスには役立ちません。 上記の例では、顧客が ArrayList 型の場合です。 end note

12.20.3.4 クエリ式の生成を解除する

フォームのクエリ式

from «x» in «e» select «x»

に変換されます。

( «e» ) . Select ( «x» => «x» )

: 例

from c in customers
select c

に変換されます。

(customers).Select(c => c)

end の例

逆のクエリ式は、ソースの要素を簡単に選択する式です。

: 翻訳の後のフェーズ (§12.20.3.6 および §12.20.3.7) では、他の変換手順で導入された逆生成クエリをソースに置き換えることで削除されます。 ただし、クエリ式の結果がソース オブジェクト自体にならないようにすることが重要です。 そうしないと、このようなクエリの結果を返すと、プライベート データ (要素配列など) が呼び出し元に誤って公開される可能性があります。 したがって、この手順では、ソースで Select を明示的に呼び出すことによって、ソース コードで直接記述された退化クエリを保護します。 その後、これらのメソッドがソース オブジェクト自体を返さないことを保証するのは、 Select およびその他のクエリ演算子の実装者にかかっています。 end note

12.20.3.5 From、let、where、join、orderby 句

2 番目の from 句の後に select 句が続くクエリ式

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

に変換されます。

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

: 例

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

に変換されます。

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

end の例

2 つ目の from 句の後に、空でないクエリ本文句のセットを含むクエリ本文 Q を含むクエリ式。

from «x1» in «e1»
from «x2» in «e2»
Q

に変換されます。

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

: 例

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

に変換されます。

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

最後の翻訳は次の内容です。

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

ここで x は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。

end の例

let式とその前の from 句:

from «x» in «e»  
let «y» = «f»  
...

に変換されます。

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

: 例

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

に変換されます。

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

最後の翻訳は次の内容です。

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

ここで x は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。

end の例

where式とその前の from 句:

from «x» in «e»  
where «f»  
...

に変換されます。

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

join句の直後にselect句が続く

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

に変換されます。

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

: 例

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

に変換されます。

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

end の例

join句の後にクエリ本文句が続きます。

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

に変換されます。

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

join-into句の直後にselect句が続く

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

に変換されます。

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

join into句の後にクエリ本文句が続く

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

に変換されます。

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

: 例

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

に変換されます。

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

最後の翻訳は次の内容です。

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

ここで、 xy は、コンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。

end の例

orderby句とその前の from 句:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

に変換されます。

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

ordering句で降順インジケーターが指定されている場合は、代わりにOrderByDescendingまたはThenByDescendingの呼び出しが生成されます。

: 例

from o in orders
orderby o.Customer.Name, o.Total descending
select o

には最終的な翻訳があります

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

end の例

次の翻訳では、 letwherejoin 句、または orderby 句がなく、各クエリ式に含まれる最初の from 句が 1 つ以下であることを前提としています。

12.20.3.6 Select 句

フォームのクエリ式

from «x» in «e» select «v»

に変換されます。

( «e» ) . Select ( «x» => «v» )

«v»が識別子«x»である場合を除き、翻訳は単純です

( «e» )

: 例

from c in customers.Where(c => c.City == "London")
select c

は単に次のように変換されます。

(customers).Where(c => c.City == "London")

end の例

12.20.3.7 Group 句

group

from «x» in «e» group «v» by «k»

に変換されます。

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

«v»が識別子«x»の場合を除き、翻訳は

( «e» ) . GroupBy ( «x» => «k» )

: 例

from c in customers
group c.Name by c.Country

に変換されます。

(customers).GroupBy(c => c.Country, c => c.Name)

end の例

12.20.3.8 透過的な識別子

特定の翻訳では、*で示transparent 識別子を持つ範囲変数が挿入されます。 透過的な識別子は、クエリ式変換プロセスの中間ステップとしてのみ存在します。

クエリ変換によって透過的な識別子が挿入されると、さらに変換手順を実行すると、透過的な識別子が匿名関数と匿名オブジェクト初期化子に伝達されます。 これらのコンテキストでは、透過的な識別子には次の動作があります。

  • 透過的な識別子が匿名関数のパラメーターとして発生すると、関連付けられた匿名型のメンバーは、匿名関数の本体のスコープ内で自動的に有効になります。
  • 透過的な識別子を持つメンバーがスコープ内にある場合、そのメンバーのメンバーもスコープ内にあります。
  • 透過的な識別子が匿名オブジェクト初期化子のメンバー宣言子として発生すると、透過的な識別子を持つメンバーが導入されます。

上記の変換手順では、透過的な識別子は、複数の範囲変数を 1 つのオブジェクトのメンバーとしてキャプチャすることを目的として、匿名型と共に常に導入されます。 C# の実装では、匿名型とは異なるメカニズムを使用して複数の範囲変数をグループ化できます。 次の翻訳の例では、匿名型が使用されることを前提とし、透過的な識別子の 1 つの翻訳の可能性を示しています。

: 例

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

に変換されます。

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

に変換されます。

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

これは、透過的な識別子が消去されるときに、〘と

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

ここで x は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。

例を示します。

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

に変換されます。

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

また、この

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

最後の翻訳は次の内容です。

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

ここで、 xy はコンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。 end の例

12.20.4 クエリ式パターン

Query 式パターンは、型がクエリ式をサポートするために実装できるメソッドのパターンを確立します。

パブリック メンバー メソッドとパブリックにアクセス可能な拡張メソッドを次のクラス定義に置き換えることができる場合、ジェネリック型 C<T> はクエリ式パターンをサポートします。 メンバーとアクセス可能な extenson メソッドは、ジェネリック型 C<T>の "shape" と呼ばれます。 ジェネリック型は、パラメーターと戻り値の型の間の適切な関係を示すために使用されますが、非ジェネリック型のパターンも実装できます。

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

上記のメソッドは、 Func<T1, R> および Func<T1, T2, R>ジェネリック デリゲート型を使用しますが、パラメーターと戻り値の型で同じリレーションシップを持つ他のデリゲート型または式ツリー型も同様によく使用できます。

: C<T>O<T>の間で推奨される関係により、OrderByまたはOrderByDescendingの結果でのみThenByメソッドとThenByDescendingメソッドを使用できるようになります。 end note

: GroupByの結果の推奨される形状。シーケンスのシーケンスです。各内部シーケンスには追加の Key プロパティがあります。 end note

: クエリ式は構文マッピングによってメソッド呼び出しに変換されるため、型はクエリ式パターンの一部またはすべてを実装する方法に大きな柔軟性を持ちます。 たとえば、2 つの呼び出し構文が同じであり、匿名関数は両方に変換できるため、メソッドはデリゲートまたは式ツリーを要求できるため、パターンのメソッドをインスタンス メソッドまたは拡張メソッドとして実装できます。 一部のクエリ式パターンのみを実装する型では、型がサポートするメソッドにマップされるクエリ式変換のみがサポートされます。 end note

: System.Linq 名前空間は、 System.Collections.Generic.IEnumerable<T> インターフェイスを実装する任意の型のクエリ式パターンの実装を提供します。 end note

12.21 代入演算子

12.21.1 全般

代入演算子の 1 つを含め、すべて変数、プロパティ、イベント、またはインデクサー要素に新しい値を割り当てます。 例外 = ref、変数参照 (§9.5) を参照変数 (§9.7) に割り当てます。

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

代入の左オペランドは、変数として分類される式、または = ref、プロパティ アクセス、インデクサー アクセス、イベント アクセス、またはタプルを除く式でなければなりません。 宣言式は左オペランドとして直接許可されませんが、分解代入の評価のステップとして発生する可能性があります。

=演算子は、simple 代入演算子と呼ばれます。 右側のオペランドの値または値を、左オペランドによって指定された変数、プロパティ、インデクサー要素、またはタプル要素に割り当てます。 単純代入演算子の左オペランドは、イベント アクセスにすることはできません ( §15.8.2 で説明されている場合を除く)。 単純代入演算子については、 §12.21.2 で説明されています。

演算子 = ref は、 ref 代入演算子と呼ばれます。 右オペランドは、左オペランドで指定された参照変数の参照先である variable_reference (§9.5) になります。 ref 代入演算子については、 §12.21.3 で説明されています。

=演算子および= ref演算子以外の代入演算子は、compound 代入演算子と呼ばれます。 これらの演算子は、2 つのオペランドに対して指定された演算を実行し、結果の値を左オペランドによって指定された変数、プロパティ、またはインデクサー要素に割り当てます。 複合代入演算子については、 §12.21.4 で説明されています。

左側のオペランドとしてイベント アクセス式を持つ += 演算子と -= 演算子は、 event 代入演算子と呼ばれます。 左側のオペランドとしてイベント アクセスを持つ他の代入演算子は有効ではありません。 イベント割り当て演算子については、 §12.21.5 で説明されています。

代入演算子は右自動調整です。つまり、操作は右から左にグループ化されます。

: a = b = c 形式の式は、 a = (b = c)として評価されます。 end の例

12.21.2 単純な割り当て

=演算子は、単純代入演算子と呼ばれます。

単純代入の左側のオペランドがフォーム E.P または E[Ei] の場合、 E にコンパイル時の型 dynamicがある場合、割り当ては動的にバインドされます (§12.3.3)。 この場合、割り当て式のコンパイル時の型が dynamicされ、次に示す解決は実行時に Eの種類に基づいて実行されます。 左側のオペランドが形式 E[Ei] で、 Ei の少なくとも 1 つの要素にコンパイル時の型 dynamicがあり、 E のコンパイル時の型が配列でない場合、結果のインデクサー アクセスは動的にバインドされますが、コンパイル時のチェックは制限されます (§12.6.5)。

左オペランドがタプルとして分類される単純な代入は、 分解代入とも呼ばれます。 左オペランドのいずれかのタプル要素に要素名がある場合は、コンパイル時エラーが発生します。 左オペランドのいずれかのタプル要素が declaration_expression であり、他の要素が declaration_expression または単純な破棄ではない場合は、コンパイル時エラーが発生します。

単純な代入x = yの型は、yxへの割り当ての型であり、次のように再帰的に決定されます。

  • xがタプル式(x1, ..., xn)であり、yn要素 (§12.7) を持つタプル式(y1, ..., yn)に分解でき、yixiに対する各割り当てが型Tiを持つ場合、代入には型(T1, ..., Tn)があります。
  • それ以外の場合、 x が変数として分類されている場合、変数は readonlyされず、 x は型 Tを持ち、 yTへの暗黙的な変換を持ち、代入には型 Tがあります。
  • それ以外の場合、 x が暗黙的に型指定された変数 (つまり、暗黙的に型指定された宣言式) として分類され、 y に型 Tがある場合、変数の推論された型は Tされ、代入には型 Tがあります。
  • それ以外の場合、 x がプロパティアクセスまたはインデクサーアクセスとして分類されている場合、プロパティまたはインデクサーはアクセス可能なセットアクセサーを持ち、 x は型 Tを持ち、 yTへの暗黙的な変換を持ち、割り当てには型 Tがあります。
  • それ以外の場合、割り当てが有効ではなく、バインド時エラーが発生します。

Tを持つフォーム x = yの単純な割り当ての実行時処理は、次の再帰的な手順で構成される型Tを持つyxへの割り当てとして実行されます。

  • x は、まだ評価されていない場合に評価されます。
  • xが変数として分類されている場合、yが評価され、必要に応じて暗黙的な変換 (§10.2) を使用してTに変換されます。
    • xによって指定された変数がreference_typeの配列要素である場合は、実行時チェックが実行され、yに対して計算された値が、xが要素である配列インスタンスと互換性があることを確認します。 ynullされている場合、または暗黙的な参照変換 (§10.2.8) が、yによって参照されるインスタンスの型から、xを含む配列インスタンスの実際の要素型に存在する場合、チェックは成功します。 それ以外の場合は、System.ArrayTypeMismatchException がスローされます。
    • yの評価と変換によって得られた値は、xの評価によって指定された場所に格納され、割り当ての結果として生成されます。
  • xがプロパティまたはインデクサー アクセスとして分類されている場合:
    • yは評価され、必要に応じて暗黙的な変換 (§10.2) を使用してTに変換されます。
    • xの set アクセサーは、yの評価と変換に起因する値をその値引数として呼び出します。
    • yの評価と変換に起因する値は、割り当ての結果として生成されます。
  • xがアリティ nを持つタプル (x1, ..., xn)として分類される場合:
    • yは、タプル式en要素を使用して分解されます。
    • 結果のタプル t は、暗黙的なタプル変換を使用して eT に変換することによって作成されます。
    • xiに対して左から右に順番に、t.Itemixiへの割り当てが実行されます。ただし、xiは再評価されません。
    • t は割り当ての結果として生成されます。

: x のコンパイル時の型が dynamic であり、 y のコンパイル時の型から dynamicへの暗黙的な変換がある場合、実行時の解決は必要ありません。 end note

: 配列の共分散規則 (§17.6) では、配列型 A[] の値を配列型 B[]のインスタンスへの参照にすることができます( B から Aへの暗黙的な参照変換が存在する場合)。 これらの規則により、 reference_type の配列要素への割り当てには、割り当てられている値が配列インスタンスと互換性があることを確認するための実行時チェックが必要です。 この例では、

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

最後の割り当てにより、ArrayListへの参照をstring[]の要素に格納できないため、System.ArrayTypeMismatchExceptionがスローされます。

end note

struct_typeで宣言されたプロパティまたはインデクサーが代入のターゲットである場合、プロパティまたはインデクサー アクセスに関連付けられているインスタンス式は変数として分類されます。 インスタンス式が値として分類されると、バインド時エラーが発生します。

: §12.8.7 のため、フィールドにも同じ規則が適用されます。 end note

: 宣言を指定します。

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

この例では

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

p.Xp.Yr.A、およびr.Bへの割り当ては、prが変数であるために許可されます。 ただし、この例では

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

r.Ar.Bは変数ではないため、代入はすべて無効です。

end の例

12.21.3 Ref 代入

= ref演算子は、ref 代入演算子と呼ばれます。

左オペランドは、参照変数 (§9.7)、参照パラメーター ( this以外)、出力パラメーター、または入力パラメーターにバインドする式でなければなりません。 右オペランドは、左オペランドと同じ型の値を指定する variable_reference を生成する式でなければなりません。

左オペランドの ref-safe-context (§9.7.2) が右オペランドの ref-safe-context よりも広い場合、コンパイル時エラーになります。

右オペランドは、ref 代入の時点で確実に割り当てられます。

左オペランドが出力パラメーターにバインドする場合、その出力パラメーターが ref 代入演算子の先頭に確実に割り当てられていない場合はエラーになります。

左オペランドが書き込み可能な ref である場合 (つまり、 ref readonly ローカルパラメーターまたは入力パラメーター以外のものを指定します)、右オペランドは書き込み可能な variable_referenceになります。 右オペランド変数が書き込み可能な場合、左オペランドは書き込み可能または読み取り専用の ref である可能性があります。

この演算により、左オペランドが右オペランド変数の別名になります。 右オペランド変数が書き込み可能な場合でも、別名を読み取り専用にすることができます。

ref 代入演算子は、割り当てられた型の variable_reference を生成します。 左オペランドが書き込み可能な場合は書き込み可能です。

ref 代入演算子は、右オペランドによって参照されるストレージの場所を読み取らないものとします。

: = refの使用例を次に示します。

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

end の例

: = ref 演算子を使用してコードを読み取るとき、オペランドの一部として ref 部分を読み取りたくなる可能性があります。 これは、オペランドが条件付き ?: 式である場合に特に混乱します。 たとえば、ref int a = ref b ? ref x : ref y;を読み取る場合は、演算子である= refとして読み取り、ref int a = ref (b ? ref x : ref y);の右オペランドb ? ref x : ref yすることが重要です。 重要なこととして、ref b式は一見すると表示される場合でも、そのステートメントの一部ではありません。 end note

12.21.4 複合代入

複合代入の左オペランドが E.P 形式または E[Ei] の場合、 E のコンパイル時型が dynamicの場合、代入は動的にバインドされます (§12.3.3)。 この場合、割り当て式のコンパイル時の型が dynamicされ、次に示す解決は実行時に Eの種類に基づいて実行されます。 左側のオペランドが形式 E[Ei] で、 Ei の少なくとも 1 つの要素にコンパイル時の型 dynamicがあり、 E のコンパイル時の型が配列でない場合、結果のインデクサー アクセスは動的にバインドされますが、コンパイル時のチェックは制限されます (§12.6.5)。

フォーム x «op»= y の操作は、2 項演算子のオーバーロード解決 (§12.4.5) を適用することによって処理 x «op» y。 このとき、次のようになります。

  • 選択した演算子の戻り値の型がxの型に暗黙的に変換できる場合、xは 1 回だけ評価される点を除き、操作はx = x «op» yとして評価されます。
  • それ以外の場合、選択した演算子が定義済みの演算子の場合、選択した演算子の戻り値の型が x の型に明示的に変換可能であり、 yx の型に暗黙的に変換できる場合、または演算子がシフト演算子である場合、操作は x = (T)(x «op» y)として評価されます。 Txの型です。ただし、 x は 1 回だけ評価されます。
  • それ以外の場合、複合代入は無効であり、バインド時エラーが発生します。

"1 回だけ評価" という用語は、 x «op» yの評価では、 x の構成式の結果が一時的に保存され、 xへの割り当てを実行するときに再利用されることを意味します。

: 割り当て A()[B()] += C()では、 Aint[]を返すメソッドで、 BCintを返すメソッドであり、メソッドは ABCの順序で 1 回だけ呼び出されます。 end の例

複合代入の左オペランドがプロパティ アクセスまたはインデクサー アクセスである場合、プロパティまたはインデクサーには get アクセサーと set アクセサーの両方が含まれます。 そうでない場合は、バインド時エラーが発生します。

上記の 2 番目の規則では、特定のコンテキストでx = (T)(x «op» y)として評価x «op»= yを許可します。 この規則は、左オペランドが sbytebyteshortushort、または charの型である場合に、定義済みの演算子を複合演算子として使用できるように存在します。 両方の引数がこれらの型のいずれかである場合でも、定義済みの演算子は、§12.4.7.3 で説明されているように、int型の結果を生成。 したがって、キャストがないと、結果を左オペランドに割り当てできなくなります。

定義済みの演算子に対するルールの直感的な効果は、x «op» yx = yの両方が許可されている場合にx «op»= yが許可されるということです。

: 次のコード内

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

各エラーの直感的な理由は、対応する単純な割り当てもエラーだったということです。

end の例

: これは、複合代入操作がリフトされた演算子をサポートすることも意味します。 複合代入 x «op»= yx = x «op» y または x = (T)(x «op» y)として評価されるため、評価の規則では、リフトされた演算子が暗黙的にカバーされます。 end note

12.21.5 イベントの割り当て

a += or -=演算子の左オペランドがイベント アクセスとして分類されている場合、式は次のように評価されます。

  • イベント アクセスのインスタンス式 (存在する場合) が評価されます。
  • +=演算子または-=演算子の右オペランドが評価され、必要に応じて、暗黙的な変換 (§10.2) を使用して左オペランドの型に変換されます。
  • 前の手順で計算した値で構成される引数リストを使用して、イベントのイベント アクセサーが呼び出されます。 演算子が +=された場合は、add アクセサーが呼び出されます。演算子が -=された場合は、remove アクセサーが呼び出されます。

イベント割り当て式は値を生成しません。 したがって、イベント代入式は、 statement_expression (§13.7) のコンテキストでのみ有効です。

12.22 式

は、non_assignment_expressionまたはassignmentです。

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 定数式

定数式は、コンパイル時に完全に評価される式です。

constant_expression
    : expression
    ;

定数式は、 null 値または次のいずれかの型を持つ必要があります。

  • sbytebyteshortushortintuintlongulongcharfloatdoubledecimalboolstring;
  • 列挙型。又は
  • 参照型の既定値の式 (§12.8.21)。

定数式では、次のコンストラクトのみが許可されます。

  • リテラル ( null リテラルを含む)。
  • クラス型と構造体型 const メンバーへの参照。
  • 列挙型のメンバーへの参照。
  • ローカル定数への参照。
  • 親子化された部分式。それ自体が定数式です。
  • 式をキャストします。
  • checked および unchecked 式。
  • nameof 式。
  • 定義済みの +-! (論理否定)、および単項演算子 ~
  • 定義済みの +-*/%<<>>&|^&&||==!=<><=、および >= バイナリ演算子。
  • ?:条件演算子。
  • ! null 許容演算子 (§12.8.9)。
  • sizeof アンマネージ型が定数値を返す §23.6.9 で指定された型の 1 つである場合 sizeof 式。
  • 型が上記の型のいずれかである場合、既定値の式。

定数式では、次の変換を使用できます。

  • ID 変換
  • 数値変換
  • 列挙型の変換
  • 定数式の変換
  • 変換元が null 値に評価される定数式である場合、暗黙的および明示的な参照変換。

: null 以外の値のボックス化、ボックス化解除、暗黙的な参照変換などのその他の変換は、定数式では使用できません。 end note

: 次のコード内

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

ボックス化変換が必要であるため、 i の初期化はエラーです。 null以外の値からの暗黙的な参照変換が必要であるため、strの初期化はエラーです。

end の例

式が上記の要件を満たすたびに、式はコンパイル時に評価されます。 これは、式が非定数コンストラクトを含む大きな式の部分式である場合でも当てはまります。

定数式のコンパイル時評価では、非定数式の実行時評価と同じ規則が使用されます。ただし、実行時の評価で例外がスローされる場合、コンパイル時の評価によってコンパイル時エラーが発生する点が異なります。

定数式が unchecked コンテキストに明示的に配置されていない限り、式のコンパイル時の評価中に整数型の算術演算および変換で発生するオーバーフローは、常にコンパイル時エラーを引き起こします (§12.8.20)。

定数式は、以下に示すコンテキストで必要であり、これは constant_expressionを使用して文法で示されます。 これらのコンテキストでは、コンパイル時に式を完全に評価できない場合、コンパイル時エラーが発生します。

  • 定数宣言 (§15.4)
  • 列挙メンバー宣言 (§19.4)
  • パラメーター リストの既定の引数 (§15.6.2)
  • caseswitch ステートメントのラベル (§13.8.3)。
  • goto case ステートメント (§13.10.4)
  • 初期化子を含む配列作成式 (§12.8.17.5) の次元の長さ。
  • 属性 (§22)
  • constant_pattern (§11.2.3)

暗黙的な定数式変換 (§10.2.11) を使用すると、定数式の値が変換先の型の範囲内にある場合、型 int の定数式を sbytebyteshortushortuint、または ulongに変換できます。

12.24 ブール式

boolean_expressionは、bool型の結果を生成する式です。次に示すように、直接または特定のコンテキストでoperator trueを適用します。

boolean_expression
    : expression
    ;

if_statementの制御条件式 (§13.8.2)、while_statement (§13.9.2) do_statement (§13.9.3)、またはfor_statement (§13.9.4) はboolean_expressionです。 ?:演算子 (§12.18) の制御条件式は、boolean_expressionと同じ規則に従いますが、演算子の優先順位の理由から、null_coalescing_expressionとして分類されます。

次のように、bool型の値を生成できるようにするには、boolean_expressionEが必要です。

  • E が bool に暗黙的に変換できる場合は、実行時に暗黙的な変換が適用されます。
  • それ以外の場合は、単項演算子のオーバーロード解決 (§12.4.4) を使用して、Eでのoperator trueの固有の最適な実装を検索し、実行時にその実装が適用されます。
  • このような演算子が見つからない場合は、バインド時エラーが発生します。