次の方法で共有


12 式

12.1 全般

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

12.2 式の分類

12.2.1 全般

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

  • 値。 すべての値には、型が関連付けられています。
  • 変数。 特に指定しない限り、変数は明示的に型指定され、関連付けられた型 (つまり、変数の宣言された型) を持ちます。 暗黙的に型指定された変数には、関連付けられた型がありません。
  • Null リテラル。 この分類を持つ式は、参照型または null 許容値型に暗黙的に変換できます。
  • 匿名関数。 この分類を持つ式は、互換性のあるデリゲート型または式ツリー型に暗黙的に変換できます。
  • タプル。 すべてのタプルには固定数の要素があり、それぞれに式とオプションのタプル要素名が付きます。
  • プロパティ アクセス。 すべてのプロパティ アクセスには、関連付けられた型 (つまり、プロパティの型) があります。 さらに、プロパティ アクセスには、インスタンス式が関連付けられている場合があります。 インスタンス プロパティ アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、 this (§12.8.14) で表されるインスタンスになります。
  • インデクサーへのアクセス。 すべてのインデクサー アクセスには、インデクサーの要素型という、関連付けられた型があります。 さらに、インデクサー アクセスには、関連付けられたインスタンス式と、関連付けられた引数リストがあります。 インデクサー アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、 this (§12.8.14) で表されるインスタンスになり、引数リストを評価した結果が呼び出しのパラメーター リストになります。
  • 何もしない。 これは、式が戻り値の型が 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) が取得されます。

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

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ᵥ]
  • オブジェクトの作成: new C(e₁,...,eᵥ)
  • オーバーロードされた単項演算子: +-! (論理否定のみ)、 ~++--truefalse
  • オーバーロードされた二項演算子: +, -, *, /, %, &, &&, |, ||, ??, ^, <<, >>, ==, !=, >, <, >=, <=
  • 代入演算子: =, = ref, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • 暗黙的な型変換と明示的な型変換

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

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

12.3.2 バインディング タイム

静的バインディングはコンパイル時に行われますが、動的バインディングは実行時に行われます。 次のサブクローズでは、 バインド時 という用語は、バインドがいつ行われるかに応じて、コンパイル時または実行時を指します。

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

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 のオーバーロードは、引数のコンパイル時の型に基づいて選択されます。 したがって、バインディング タイムは、コンパイル時です。

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

終了サンプル

12.3.3 動的バインディング

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

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

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

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

12.3.4 部分式の型

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

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

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

12.4 オペレーター

12.4.1 全般

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

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

演算子には 次のような 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 で呼び出されます。 これは、演算子の優先順位とは別であり、関連性がありません。 終了サンプル

特定の演算子は、オーバーロードされることができます。 演算子をオーバーロード (§12.4.3) すると、ユーザー定義演算子の実装を、1 つまたは両方のオペランドがユーザー定義のクラスまたは構造体型である演算子に指定することができます。

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

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

: たとえば、式 x + y * z の評価は x + (y * z) ですが、これは * 演算子が + 演算子より高い優先順位だからです。 注釈

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

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

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

サブ節 カテゴリ 演算子
§12.8 主要 x.yx?.yf(x)a[x]a?[x]x++x--x!newtypeofdefaultcheckeduncheckeddelegatestackalloc
§12.9 単項演算子 +-!x~++x--x(T)xawait x
§12.10 乗法 */%
§12.10 加法 +-
§12.11 シフト <<>>
§12.12 関係式と型検査 <><=>=isas
§12.12 等価比較 ==!=
§12.13 論理積 &
§12.13 論理 XOR ^
§12.13 論理和 \|
§12.14 条件付き AND &&
§12.14 条件 OR \|\|
§12.15§12.16 null 合体と throw 式 ??throw x
§12.18 条件付き ?:
§12.21 および §12.19 代入とラムダ式 == ref*=/=%=+=-=<<=>>=&=^=\|==>

注釈

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

  • 代入演算子と null 合体演算子を除き、すべての二項演算子は 左からの結合であり、つまり演算は左から右に実行されます。

    : x + y + z(x + y) + zと評価されます。 終了サンプル

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

    : x = y = zx = (y = z)と評価されます。 終了サンプル

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

: x + y * z は最初に yz を掛け算し、x に結果を足しますが、(x + y) * z は、まず xy を足してからその結果と z を掛け算します。 終了サンプル

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

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

オーバーロード可能な単項演算子 は次のとおりです。

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

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

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

オーバーロード可能な 2項演算子 は次のとおりです。

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

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

2項演算子をオーバーロードすると、対応する複合代入演算子が存在すれば、それも暗黙的にオーバーロードされます。

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

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

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

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

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

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

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

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

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

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

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

: / 演算子は常に 2 項演算子であり、常に §12.4.2で指定された優先順位レベルを持ち、常に左から連想されます。 終了サンプル

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

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

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

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

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

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

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

  • 演算 XYoperator «op»(x, y) によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、 X によって提供される候補演算子と Yによって提供される候補演算子の和集合で構成され、それぞれ §12.4.6の規則を使用して決定されます。 結合セットの場合、候補は次のようにマージされます。
    • XY が ID 変換可能な場合、または XY が共通の基本型から派生している場合、共有候補演算子は結合セット内で 1 回だけ発生します。
    • XYの間に ID 変換がある場合、 «op»Y によって提供される演算子 Y は、 «op»X によって提供される 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 は引数リスト) を指定すると、演算子 T«op»(A) によって提供される候補ユーザー定義演算子のセットは次のように決定されます。

  • T₀ 型を決定します。 T が null 許容値型の場合、 T₀ はその基になる型です。それ以外の場合、 T₀Tと等しくなります。
  • T₀ 内のすべての operator «op» 宣言とそのような演算子のすべてのリフトされた形式について、引数リスト Aに関して少なくとも1つの演算子が適用可能(§12.6.4.2)である場合、候補演算子の集合は 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) がこの一連の演算子に適用されている場合、オペランド型から暗黙的な変換が存在する最初の演算子を選択します。

: 演算 b * sの場合、 bbytesshortで、オーバーロードの解決では最適な演算子として operator *(int, int) が選択されます。 その結果、 bsintに変換され、結果の型は intになります。 同様に、 i * di で、 intdである演算 doubleの場合、 overload 解像度は最適な演算子として operator *(double, double) を選択します。 終了サンプル

情報テキストの末尾。

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 型の間に暗黙的な変換が存在しないという事実に従います。 注釈

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

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

: 次のコード例の内容:

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

終了サンプル

情報テキストの末尾。

12.4.8 リフト演算子

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

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

12.5 メンバー検索

12.5.1 全般

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

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

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

NK 型引数がある名前 T のメンバー検索は、以下のように処理されます。

  • まず、 N という名前のアクセス可能なメンバーのセットが決定されます。
    • T が型パラメータの場合、セットは、 T のプライマリ制約またはセカンダリ制約 (§15.2.5) として指定された各型で N という名前のアクセス可能なメンバーのセットと、 objectN という名前のアクセス可能なメンバーのセットの和集合です。
    • それ以外の場合、N という名前のアクセス可能な (T) メンバーすべてで構成されます。これには、N の継承されたメンバーと object という名前のアクセス可能なメンバーが含まれます。 T が構築された型の場合、メンバーのセットは、 §15.3.3で説明されているように型引数を置き換えることによって取得されます。 override 修飾子を含むメンバーは、セットから除外されます。
  • 次に、 K が 0 の場合、宣言に型パラメーターが含まれる入れ子になった型はすべて削除されます。 K が 0 でない場合、型パラメーターの数が異なるメンバーはいずれも削除されます。 K が 0 の場合、型推論プロセス (§12.6.3) が型引数を推論できる可能性があるため、型パラメーターを持つメソッドは削除されません。
  • 次に、メンバーが呼び出されると、呼び出し不可能なすべてのメンバーがセットから削除されます。
  • 次に、他のメンバーによって非表示になっているメンバーがセットから削除されます。 セット内の各メンバー S.M の場合、S は、メンバー M が宣言されている型となり、次の規則が適用されます。
    • M が定数、フィールド、プロパティ、イベント、または列挙メンバーの場合、基本型の S で宣言されているすべてのメンバーがセットから削除されます。
    • M が型宣言の場合、基本型の S で宣言されているすべての非型がセットから削除され、基本型の M で宣言 S と同じ数の型パラメーターを持つすべての型宣言がセットから削除されます。
    • 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.2で説明されているように、あいまいさのためにメソッド グループの使用がさらに失敗する可能性があります。 注釈

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)。 注釈

  • Tclass_typeの場合、 T の基本型は、クラス型 Tを含む、 objectの基底クラスです。
  • Tinterface_typeの場合、 T の基本型は T の基本インターフェイスであり、クラス型は objectです。
  • Tarray_typeの場合、 T の基本型は、 System.Arrayobjectクラス型です。
  • 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 つのカテゴリの関数メンバーを含むコンストラクトで行われる処理をまとめたものです。 表中の exyvalue は変数または値として分類される式、 T は型として分類される式、 F はメソッドの単純名、 P はプロパティの単純名を示します。

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

注釈

12.6.2 引数リスト

12.6.2.1 全般

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

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

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

  • プロパティの場合、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_name の後に argument_valueで構成されます。 argument_name を持つ 引数名前付き引数と呼ばれますが、 argument_name のない 引数位置引数です。

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

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

このフォームは、引数の パラメーター渡しモードを として決定します。それぞれ、入力参照、または 出力です。 ただし、前述のように、値渡しモードの引数は、入力渡しモードのものに変換される可能性があります。

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

12.6.2.2 対応するパラメーター

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

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

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

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

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

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

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

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

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

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

    • 引数式が評価され、対応するパラメーター型への暗黙的な変換 (§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を参照してください。

      終了サンプル

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

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

: 次のコード例の内容:

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 回目の呼び出しは、System.ArrayTypeMismatchException の実際の要素型が b であり、string ではないため、object がスローされる原因となります。

終了サンプル

メソッド、インデクサー、およびインスタンス コンストラクターは、その右端のパラメーターをパラメーター配列として宣言できます (§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

終了サンプル

パラメーター配列を持つ関数メンバーが展開された形式で少なくとも 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 });

終了サンプル

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

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

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

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

終了サンプル

型推論は、メソッド呼び出し (§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ᵢ に対して固定となるか、境界一式が関連付けられた未固定となります。各境界は、なんらかの T 型です。 最初に、 Xᵢ 各型変数は、空の境界セットで固定解除されます。

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

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

12.6.3.2 第1フェーズ

各メソッド引数Eᵢに対して

  • Eᵢ が匿名関数の場合、 明示的なパラメーター型推論 (§12.6.3.8) が からEᵢTᵢ行われます
  • それ以外の場合、 Eᵢ に型 U があり、対応するパラメーターが値パラメーター (§15.6.2.2) である場合、 下限推論 (§12.6.3.10) が からUTᵢ行われます。
  • それ以外の場合は、 に型 があり、対応するパラメーターが参照パラメーター (§15.6.2.3.3) または出力パラメーター (§15.6.2.3.4) である場合、から正確な推論 (§12.6.3.9) がなされます。
  • それ以外の場合、 Eᵢ の型が U であり、対応するパラメータが入力パラメータ(§15.6.2.3.2)であり、 Eᵢ が入力引数である場合、 正確な推論 (§12.6.3.9) が からUTᵢ行われます。
  • それ以外の場合、 Eᵢ に型 U があり、対応するパラメーターが入力パラメーター (§15.6.2.2) である場合、 下限推論 (§12.6.3.10) が UからTᵢ行われます。
  • それ以外の場合、この引数の推論は行われません。

12.6.3.3 第2フェーズ

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

  • すべての未固定の型変数 Xᵢ は、(§12.6.3.6) に依存しません。すべての Xₑ は固定されます (§12.6.3.12)。
  • このような型変数が存在しない場合、未固定の 型変数 Xᵢ はすべて、次の条件が満たされる固定型 になります。
    • Xᵢに依存する型変数 Xₑ が少なくとも1つあります
    • Xᵢ に空でない境界セットがある
  • そのような型変数が存在せず、 未解決 の型変数がまだ存在する場合、型推論は失敗します。
  • それ以外の場合、 修正されていない 型変数がそれ以上存在しない場合、型の推論は成功します。
  • それ以外の場合は、対応するパラメータ型 Tᵢ (出力型 が (§12.6.3.5)) があるすべての引数 Eᵢ には、未固定型変数Xₑが含まれますが、入力型 (§12.6.3.4) は含まれません。出力型推論 (§12.6.3.7) は、Eᵢ から Tᵢ で作成されます。 その後、第2フェーズが繰り返されます。

12.6.3.4 入力型

がメソッド グループまたは暗黙の型指定された匿名関数であり、 がデリゲート型または式ツリー型である場合、 のすべてのパラメーター型は、型のの入力型 です。

12.6.3.5 出力の種類

がメソッド グループまたは匿名関数であり、 がデリゲート型または式ツリー型である場合、 の戻り値の型は、型がである 出力型になります。

12.6.3.6 依存関係

unfixed 型変数は Xᵢは、直接 に依存し、引数Eᵥ に型 TᵥXₑ がある場合、unfixed 型変数 Xₑは、Tᵥ がある Eᵥ入力型で発生し、Xᵢ は、型 Tᵥ がある Eᵥ出力型で発生します。

Xₑ直接依存Xᵢ する場合、または Xᵢ直接依存Xᵥ し、 Xᵥ依存Xₑする場合、 Xₑ依存Xᵢ します。 したがって、「に依存する」は「に直接依存する」の推移閉包であり、再帰的な閉包ではない。

12.6.3.7 出力型の推論

出力型推論が、次の方法で、式 E型 T 行われた場合:

  • E が、推論された戻り型 U (§12.6.3.13) がある匿名関数であり、T がデリゲート型または戻り値の型 Tₓ がある式木型の場合、lower-bound 継承 (§12.6.3.10) は、からUTₓ行われます。
  • それ以外の場合、 E がメソッド グループであり、 T がパラメーター型 T₁...Tᵥ と戻り値の型 Tₓ を持つデリゲート型または式ツリー型であり、型 T₁...Tᵥ による E のオーバーロード解決によって戻り値の型 U を持つ単一のメソッドが生成されると、 UからTₓ への 下限推論 が行われます。
  • それ以外の場合、 E が型 U の式である場合、 下限推論UからTまで行われます。
  • それ以外の場合、推論は行われません。

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

明示的なパラメータ型推論は、以下の方法でE から型T行われます。

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

12.6.3.9 正確な推論

UからV への厳密推論は、次のように行われます。

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

12.6.3.10 下限推論

UVは、次のように行われます。

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

12.6.3.11 上限推論

UからVupper-bound 推論は、次のように行われます。

  • V固定されていないXᵢ の 1 つである場合、 UXᵢの上限セットに追加されます。
  • それ以外の場合、 V₁...VₑU₁...Uₑ のセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。
    • U は配列型 U₁[...]であり、 V は同じランクの配列型 V₁[...]です
    • U は、 IEnumerable<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ᵢ が参照型であることが不明な場合は、 正確な推論 が行われます
    • それ以外の場合、 V が配列型の場合は、 上限推論 が行われます
    • それ以外の場合、 UC<U₁...Uₑ> 場合、推論は i-thC 型パラメーターに依存します。
      • 共変の場合は、 上限推論 が行われます。
      • 反変の場合、 下限推論 が行われます。
      • 不変の場合は、 正確な推論 が行われます。
  • それ以外の場合、推論は行われません。

12.6.3.12 修正

一連の境界を持つ未固定型変数 Xᵢ は、次のように固定されます

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

12.6.3.13 推論された戻り値の型

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

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

  • F の本体が型を持つ である場合、 F の推定有効な戻り値の型はその式の型になります。
  • F の本体が ブロック で、ブロックの return ステートメント内の式のセットに最も一般的な型 T (§12.6.3.15) がある場合、 F の推定される有効な戻り値の型は Tです 。
  • それ以外の場合、 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と推論します。 次に、前述の匿名関数型推論プロセスを使用して、 c に型 Customerが与えられ、式 c.Name はセレクター パラメーターの戻り値の型に関連し、 TResultstringであると推論され、 したがって、呼び出しは

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 パラメーターに関連し、Xstringとして推論されます。 次に、最初の匿名関数のパラメーター sには推論された型 stringが与えられ、式 TimeSpan.Parse(s)f1の戻り値の型と関連しており、YSystem.TimeSpanであることが推論されます。 最後に、2 番目の匿名関数のパラメーター t には推論された型 System.TimeSpan が与えられ、式 t.TotalHoursf2 の戻り値の型に関連付けられ、 Zdouble であると推論されます。 したがって、呼び出しの結果は double型になります。

終了サンプル

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

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

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

とデリゲート型 M に割り当てられるメソッドグループ D と等しくなり、継承型のタスクは、型引数 S₁...Sᵥ を検索するため、式は次のようになります。

M<S₁...Sᵥ>

Dと互換性を持つ(§20.2)とします。

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

代わりに、すべての Xᵢ は、未固定としてみなされ、lower-bound 継承は、Dの引数型 UₑからM の対応するパラメータ型 Tₑ 行われます。 いずれかの Xᵢ で境界が見つからなかった場合、型の推論は失敗します。 それ以外の場合、すべての Xᵢ は、型推論の結果である対応する Sᵢ

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

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

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

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

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

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) に適用できる関数メンバーに縮小されます。 この縮小セットが空の場合、コンパイル時エラーが発生します。
  • 次に、適用可能な候補関数メンバーのセットから最適な関数メンバーが配置されます。 セットに含まれる関数メンバーが 1 つだけの場合、その関数メンバーが最適な関数メンバーになります。 それ以外の場合、各関数メンバーが §12.6.4.3の ルールを使用して他のすべての関数メンバーと比較される場合、特定の引数リストに関して他のすべての関数メンバーよりも優れている 1 つの関数メンバーが最適な関数メンバーになります。 他のすべての関数メンバーよりも優れた関数メンバーが 1 つだけ存在しない場合、関数メンバーの呼び出しはあいまいになり、バインド時エラーが発生します。

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

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
}

終了サンプル

  • 静的メソッドは、メソッド グループが型を介して 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 ベター関数メンバー

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

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

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

引数式のセット {E₁, E₂, ..., Eᵥ} とパラメータ型 {P₁, P₂, ..., Pᵥ}{Q₁, Q₂, ..., Qᵥ}を持つ2つの適用可能な関数メンバー MᵥMₓ を含む引数リスト A が与えられた場合、 MᵥMₓ よりも 優れた関数メンバー であると定義されます。

  • 各引数に対して、 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 より具体的でなく、少なくとも 1 つのパラメータについて RxSx より具体的である場合、 Mₓのパラメータ タイプより具体的です。
    • 型パラメーターは、型以外のパラメーターよりも具体的ではありません。
    • 構築された型が他の構築型(同じ数の型引数を持つ)より具体的とされるのは、少なくとも1つの型引数がより具体的で、かつどの型引数も他の型の対応する型引数より具体的でないことがない場合です。
    • 配列型は、1 つ目の要素型が 2 番目の要素型よりも具体的な場合、(次元数が同じ) 別の配列型よりも具体的です。
  • それ以外の場合は、1 つのメンバーが非リフト演算子で、もう一方がリフトされた演算子である場合は、非リフト演算子の方が適しています。
  • どちらの関数メンバーも良好でなく、 Mᵥ のすべてのパラメーターに対応する引数があるのに対し、既定の引数は Mₓの少なくとも 1 つの省略可能なパラメーターに置き換える必要がある場合、 MᵥMₓよりも優れています。
  • 少なくとも 1 つのパラメーター Mᵥ で、 Mₓ の対応するパラメーターよりも 優れたパラメーターパッシング選択 (§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 parameter-passing mode choice になります。

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

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₁)。そして、T₂ は変換 C₂ のためのメソッド グループでの最適な単一メソッドとは互換性がありません。

12.6.4.6 厳密に一致する式

E と型 Tが、次のいずれかの条件を満たす場合、ET と完全に一致します。

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

12.6.4.7 より優れたコンバージョンターゲット

2 種類の がある場合、次のいずれかを満たす場合には、 よりも の 優れた変換ターゲットです。

  • 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₁intS₂uint、または ulong
    • S₁long であり、S₂ulong である

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

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

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

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

終了サンプル

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

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

  • デリゲート呼び出し (§12.8.10.4) の場合、リストは呼び出しの delegate_type と同じパラメーター リストを持つ単一の関数メンバーです
  • メソッドの呼び出し (§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 は、その一時ローカル変数への参照として再分類されます。 一時変数は、 this 内で Mとしてアクセスできますが、他の方法ではアクセスできません。 したがって、E が書き込み可能な場合に限り、呼び出し元は Mthisに加える変更を観察することができます。

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

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

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

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

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

12.7 分解

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

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

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

ここで、 __v__v1, ..., __vn は、それ以外では表示されずアクセスできない一時変数を指します。

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

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_access および pointer_element_access) のセットの一部であるため、ANTLR 対応ではありません。 標準の手法を使用して文法を変換し、相互左再帰を削除できます。 すべての解析戦略がそれを必要とするわけではなく(たとえば、LALRパーサーは必要としません)、それを行うと構造と説明が難読化されるからです。 注釈

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 個以上の { および } 文字で区切られた 補間があり、それぞれが、および任意の書式指定を囲います。

挿入文字列式には2つの形式があります。通常形式(interpolated_regular_string_expression)と逐語形式(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 つの語彙規則は、文脈に依存している とされています。

規則 コンテキスト要件
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 または、このような補間内の任意の interpolated_regular_string_expression を含む、regular_interpolation の一部になることができるトークンである場合。
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End これら3つの規則の理解は、上記の対応する規則に基づき、通常の 文法規則が、対応する 逐語的な 規則に置き換えられることで成り立っています。

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

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

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

注意: interpolated_string_expression で使用できる型の違いは、System.String (§C.2) と System.FormattableString (§C.3) のドキュメントを参照することで決定できます。 注釈

補間(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) 内の他の型で確認できます。 注釈

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

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

上記のフォーマットと補間のパディングを含む interpolated_string_expression の全体的な意味は、式をメソッド呼び出しに変換することによって定義されます。式の型が System.IFormattable または System.FormattableString の場合、そのメソッドは System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) であり、型 System.FormattableString の値を返します。それ以外の場合、型は string で、メソッドは string.Format (§C.2) であり、型 string の値を返します。

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

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

  • Interpolated_Regular_String_Start の文字または Interpolated_Verbatim_String_Start の文字
  • 該当する場合は、Interpolated_Regular_String_Mid の文字またはInterpolated_Verbatim_String_Mid の文字
  • N ≥ 1からIへの0各メンバーの N-1 の場合:
    • プレースホルダーの仕様:
      • 左中かっこ ({) 文字
      • I の 10 進表現
      • 次に、対応する regular_interpolation または verbatim_interpolationinterpolation_minimum_widthがある場合は、コンマ (,) の後に constant_expression の値の 10 進表現が続きます。
      • Regular_Interpolation_Format の文字または Verbatim_Interpolation_Format の文字、該当する場合、対応する regular_interpolation または verbatim_interpolation
      • 右中かっこ (}) 文字
    • 該当する場合、対応する補間の直後の 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と同義
$"{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"

終了サンプル

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 がジェネリック メソッド宣言内に出現するが、その method_declaration属性 外にある場合、その宣言に名前 Iを持つ型パラメーターが含まれている場合、 simple_name はその型パラメーターを参照します。
  • それ以外の場合は、各インスタンス型 T (§15.3.2) に対して、すぐに外側の型宣言のインスタンス型から始まり、外側の各クラスまたは構造体宣言のインスタンス型 (存在する場合) を続行します。
    • e が 0 で、 T の宣言に名前 Iを持つ型パラメーターが含まれている場合、 simple_name はその型パラメーターを参照します。
    • それ以外の場合、 内で I 型引数を用いた T のメンバー参照 (e) が一致を生じる場合:
      • T が、すぐに囲むクラスまたは構造体型のインスタンス型であり、検索で 1 つ以上のメソッドが識別される場合、結果は、 thisのインスタンス式が関連付けられたメソッド グループになります。 型引数リストが指定されている場合は、ジェネリック メソッド (§12.8.10.2) の呼び出しで使用されます。
      • それ以外の場合、 T が直接囲むクラスまたは構造体のインスタンスの型であり、検索がインスタンス メンバーを識別し、参照がインスタンス コンストラクター、インスタンス メソッド、またはインスタンス アクセサーの ブロック 内で発生する場合 (§12.2.1)、結果は形式 this.Iのメンバー アクセス (§12.8.7) と同じになります。 これは、 e が 0 の場合にのみ発生します。
      • それ以外の場合、結果はフォーム T.I または T.I<A₁, ..., Aₑ>のメンバー アクセス (§12.8.7) と同じです。
  • それ以外の場合は、Nごとの名前空間に対して、simple_name が存在する名前空間から始まり、外側の各名前空間 (存在する場合) を続行し、グローバル名前空間で終わるまで、エンティティが見つかるまで、次のステップが評価されます。
    • e が 0 で、 IN内の名前空間の名前である場合は、次のようになります。
      • simple_name が出現する場所が N の名前空間宣言で囲まれており、その名前空間宣言に、名前 I を名前空間または型に関連付ける extern_alias_directive または using_alias_directive が含まれている場合、 simple_name はあいまいになり、コンパイル時エラーが発生します。
      • それ以外の場合、 simple_nameIN という名前の名前空間を参照します。
    • それ以外の場合、 N に名前 I および e 型パラメーターを持つアクセス可能な型が含まれている場合は、次のようになります。
      • e がゼロで、 simple_name が発生する場所が N の名前空間宣言で囲まれており、名前空間宣言に名前 I を名前空間または型に関連付ける extern_alias_directive または using_alias_directive が含まれている場合、 simple_name はあいまいであり、コンパイル時エラーが発生します。
      • それ以外の場合、 namespace_or_type_name は、指定された型引数で構築された型を参照します。
    • それ以外の場合、simple_name が発生する場所が N の名前空間宣言で囲まれている場合:
      • e がゼロで、名前空間宣言に、名前 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) の処理における対応するステップと完全に平行です。 注釈

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

12.8.5 括弧で囲まれた式

括弧で囲まれた式は、括弧で囲われたで構成されます。

parenthesized_expression
    : '(' expression ')'
    ;

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

12.8.6 タプル式

tuple_expression はタプルを表現し、2 つ以上のコンマ区切りの括弧内の (任意で命名可) で構成されています。 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_expressionvar (e1, ..., en)tuple_expression(var e1, ..., var en) の短縮形であり、同じ動作に従います。 これは、deconstruction_expression でネストされた deconstruction_tuple に再帰的に適用されます。 したがって、deconstruction_expression でネストされた各識別子は、宣言式 (§12.17) を導入します。 その結果、deconstruction_expression は単純な代入の左側でのみ現れることができます。

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

  • 対応する位置のタプル要素に名前 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))) { ... };

この例は、タプル式がメソッド呼び出しの唯一の引数である場合に、タプルがしばしば括弧が重なった複数の層を形成することになることを示しています。

終了サンプル

12.8.7 メンバー アクセス

12.8.7.1 全般

member_access は、primary_expressionpredefined_type または qualified_alias_member で構成され、「.」トークン、識別子が続き、任意で、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ₑ> からのいずれかで、E は、primary_expressionpredefined_type または qualified_alias_member で、I は、単一識別子で、<A₁, ..., Aₑ> は任意の type_argument_list です。 type_argument_list が指定されていない場合は、 e を 0 にすることを検討してください。

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

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

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

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

    • I が型を識別する場合、結果は、指定された型引数を使用して構築された型になります。
    • I が 1 つ以上のメソッドを識別する場合、結果はインスタンス式が関連付けられていないメソッド グループになります。
    • I が静的プロパティを識別する場合、結果は、関連付けられたインスタンス式のないプロパティ アクセスになります。
    • I が静的フィールドを識別する場合:
      • フィールドが読み取り専用で、フィールドが宣言されているクラスまたは構造体の静的コンストラクターの外部で参照が発生した場合、結果は値(つまり、 Eにおける静的フィールド I の値) になります。
      • それ以外の場合、結果は変数、つまり Eにおける静的フィールド I です。
    • I が静的イベントを識別する場合:
      • イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations (§15.8.1) なしで宣言された場合、 E.I は、 I が静的フィールドであるかのように正確に処理されます。
      • それ以外の場合、結果はインスタンス式が関連付けられていないイベント アクセスであることになります。
    • I が定数を識別する場合、結果は値、つまりその定数の値になります。
    • I が列挙メンバーを識別する場合、結果は値 (つまり、その列挙メンバーの値) になります。
    • それ以外の場合、 E.I は無効なメンバー参照であり、コンパイル時エラーが発生します。
  • E がプロパティ アクセス、インデクサー アクセス、変数、または値であり、その型が T であり、 T 内の IK 型引数のメンバー検索 (§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 が単一の識別子であり、 Esimple_name (§12.8.4) としての意味が、 Etype_name (§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 フィールドを参照するものは区切られません。

終了サンプル

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

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

null_conditional_member_access は、primary_expression で構成され、「?」と「.」のトークン、任意の type_argument_list がある識別子が続き、その後に、0 以上の dependent_access が続きます。これは、null_forgiving_operator の前に配置できます。

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 回だけです。

  • それ以外の場合:

    Tの型として P.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₁

Pnull であれば、A₀A₁ も評価されません。 式が null_conditional_member_access および null_conditional_element_accessを含む一連の操作である場合も同様です (§12.8.13)。

注釈

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

12.8.9 Null-forgiving 式

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 許容演算子の定義は修正されます。 注釈

同じ式に対して 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)

終了サンプル

このサブクローズと次の兄弟サブクローズの残りの部分は、条件付きかつ規範的です。

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

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

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

12.8.9.2 "null となるかもしれない" 判定を上書きする

状況によっては、コンパイラの静的 null 状態分析によって、式に 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 である可能性のある値の逆参照" 警告は !を使用して抑制できます。

終了サンプル

例: 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 にキャストできないため例外がスローされます。

終了サンプル

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

上記のように 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 は、出力パラメータであり、シンプルな割り当てが実行されます。

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

終了サンプル

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

12.8.10 呼び出し式

12.8.10.1 全般

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

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

delegate_type がある場合に限り、primary_expression は、null_forgiving_expression となる場合があります。

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

  • primary_expression にはコンパイル時の型 dynamicがあります。
  • 省略可能な引数リスト の中に少なくとも 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) の文中でのみ、'nothing' と分類される式が許可されます。 それ以外の場合は、バインド時エラーが発生します。
  • それ以外の場合、 invocation_expression が returns by-ref メソッド (§15.6.1) または returns by-ref デリゲートを呼び出した場合、結果はメソッドまたはデリゲートの戻り値の型に関連付けられた型を持つ変数になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型 Tの場合、関連付けられている型は、 T で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。
  • それ以外の場合、 invocation_expression が returns by-value メソッド (§15.6.1) または returns by-value デリゲートを呼び出した場合、結果はメソッドまたはデリゲートの戻り値の型に関連付けられた型を持つ値になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型 Tの場合、関連付けられている型は、 T で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。

12.8.10.2 Method Invocations

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

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

  • メソッド呼び出しの候補メソッドのセットが構築されます。 メソッド グループ F に関連付けられているメソッド M ごとに、次の手順を実行します。
    • 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 以外の有効な基底クラスと空でない有効なインターフェイス セットを持つ型パラメーターに対するメンバー参照の結果である場合にのみ有効です。 注釈

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

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

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

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ₑ の名前は、識別子です
  • Mₑ はアクセス可能であり、上記のように静的メソッドとして引数に適用する場合に適用できます
  • 明示的な ID、参照、ボックス化された変換は、expr から Mₑ の最初のパラメータ型へ存在します。

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

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

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

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

上記の規則は、インスタンス メソッドが拡張メソッドよりも優先されることを意味し、内部名前空間宣言で使用できる拡張メソッドは、外側の名前空間宣言で使用できる拡張メソッドよりも優先され、名前空間で直接宣言された拡張メソッドは、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の両方よりも優先されます。

終了サンプル

12.8.10.4 デリゲートの呼び出し

デリゲート呼び出しの場合、 invocation_expressionprimary_expressiondelegate_typeの値になります。 さらに、 delegate_typedelegate_typeと同じパラメーター リストを持つ関数メンバーであると考えると、 delegate_type は、 invocation_expressionargument_list に対して適用可能 (§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_expressionE は、 P?A形式です。ここで、 A は構文上同等の null_conditional_member_access または null_conditional_element_accessの残りの部分であるため、 A. または [で始まります。 PAPAの連結を意味します。

Estatement_expression として発生した場合、 E の意味は、 ステートメントの意味と同じです。

if ((object)P != null) PA

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

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

  • E が何も分類されない場合、その意味は ブロックの意味と同じです。

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

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

  • それ以外の場合、 E の意味は、 ブロックの意味と同じです。

    { 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 つ以上の 引数コンマで区切って構成されます。

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 の意味を判断するために、コンパイル時型 がある primary_no_array_creation_expression 式および dynamic 式のコンパイル時の型ではなく、実行時の型を使用して実行時に適用されます。 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_expression は、array_type の値になります。 さらに、配列アクセスの argument_list に名前付き引数を含めることはできません。 argument_list 内の式の数は、 array_typeのランクと同じである必要があります。各式は、 intuintlong、または ulong, の型であるか、暗黙的にこれらの型の 1 つ以上に変換可能である必要があります。

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

形式 P[A] (P は、array_typeprimary_no_array_creation_expression で、A は、argument_list) の配列アクセスの実行時処理は、次の手順で構成されています。

  • P が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。
  • argument_list のインデックス式は、左から右の順に評価されます。 各インデックス式の評価の後、暗黙的な変換 (§10.2) を intuintlongulongのいずれかの型に変換します。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 たとえば、インデックス式が型 short の場合は、 int から short への暗黙的な変換、および int から short への暗黙的な変換が可能であるため、 long への暗黙的な変換が実行されます。 インデックス式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上のインデックス式は評価されません。それ以上の手順は実行されません。
  • 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[A] (P は、クラス、構成、インターフェイス型 T で、A は、argument_list) のインデクサ アクセスのバインディング時処理は、次の手順で構成されています。

  • T によって提供されるインデクサーのセットが構築されます。 セットは、 T で宣言されたすべてのインデクサー、またはオーバーライド宣言ではなく現在のコンテキストでアクセス可能な T の基本型で構成されます (§7.5)。
  • 対応可能で他のインデクサーによって非表示にされていないインデクサーにこのセットは縮小されます。 次の規則は、セット内の各インデクサー S.I に適用されます。ここで、 S はインデクサー I が宣言されている型です。
    • IA (§12.6.4.2) に対して適用できない場合、 I はセットから削除されます。
    • A (§12.6.4.2) に対して I が適用される場合、基本型の 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_access は、 element_access (§12.8.7) の条件付きバージョンであり、結果の型が voidである場合はバインディング時間エラーです。 結果の型が void である可能性がある null 条件式については、(§12.8.11)を参照してください。

null_conditional_element_accessEP?[A]B 形式で、B は、dependent_access になります (該当する場合)。 E の意味は次のように決定されます。

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

    Tの型として P.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 回だけです。

  • それ以外の場合:

    Tの型として P[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₁]

Pnull に評価される場合、A₀A₁ も評価されません。 式がnull_conditional_element_accessまたはnull_conditional_member_access§12.8.8の操作を含む一連のものである場合も同様です。

注釈

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 変数は、メソッドまたはアクセサーが呼び出された構造体の コピー を表し、構造体型の パラメーターとまったく同じように動作します。

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

12.8.15 ベース アクセス

base_access は、「.」トークンまたは識別子のいずれかが続くキーワード base で構成され、オプションの type_argument_list または argument_list は、角かっこで囲まれます。

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

base_access は、現在のクラスまたは構造体の同様の名前付きメンバーによって非表示になっている基底クラスのメンバーにアクセスするために使用されます。 base_access は、インスタンス コンストラクター、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはファイナライザーの本体でのみ許可されます。 クラスまたは構造体で base.I が発生した場合、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) のコンテキストでのみ使用されるキーワードです。 注釈

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 new 演算子

12.8.17.1 全般

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

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

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

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

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

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とします。 は、 tuple_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)、次の規則が、コンパイル時の型 dynamic がある argument_list の引数のランタイム時の型を使用してランタイム時に適用されます。 ただし、オブジェクトの作成では、 §12.6.5で説明されているように、コンパイル時のチェックが制限されます。

フォーム new T(A)object_creation_expression のバインド時処理 (Tclass_typeまたは value_typeA は省略可能な argument_list) は、次の手順で構成されます。

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

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

new 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 は、初期化のターゲットを指定する必要があります。 識別子 は、初期化するオブジェクトのアクセス可能なフィールドまたはプロパティに名前を付けますが、角かっこで囲まれた argument_list は、初期化するオブジェクトのアクセス可能なインデクサーの引数を指定する必要があります。 オブジェクト初期化子が同じフィールドまたはプロパティに対して複数のメンバー初期化子を含めるのはエラーです。

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

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

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

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

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

終了サンプル

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

終了サンプル

コレクション初期化子が適用されるコレクション オブジェクトは、 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 は、それ以外の場合は非表示でアクセスできない一時的な変数です。

終了サンプル

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[,] の配列インスタンスを生成し、配列作成式 new int[10][,] は型 int[][,] の配列インスタンスを生成します。 終了サンプル

式リスト内の各式は、 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 番目の形式の配列作成式は、 暗黙的に型指定された配列作成式と呼ばれます。 これは 2 番目の形式に似ていますが、配列の要素型は明示的に指定されていませんが、配列初期化子内の式のセットの最適な共通型 (§12.6.3.15) として決定されます。 多次元配列 (つまり、 rank_specifier に少なくとも 1 つのコンマが含まれる配列) の場合、このセットは、入れ子になった array_initializerで見つかったすべての で構成されます。

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

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

  • expression_list の次元の長さの式は、左から右の順に評価されます。 各インデックス式の評価の後、暗黙的な変換 (§10.2) を次のいずれかの型に変換します。 intuintlongulong。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上の式は評価されません。それ以上の手順は実行されません。
  • ディメンションの長さの計算値は、次のように検証されます。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];
}

終了サンプル

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

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

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

注釈

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

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[]する型を指定します。 または、いずれかの要素を共通の基本型にキャストして、推論される要素型にすることができます。

終了サンプル

暗黙的に型指定された配列作成式を匿名オブジェクト初期化子 (§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" }
    }
};

終了サンプル

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 および Eexpression である形式new D(E)delegate_creation_expression のバインディング時の処理は、次の手順で構成されます。

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

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

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

Ddelegate_type および Eexpression である形式new D(E)delegate_creation_expression の実行時の処理は、次の手順で構成されます。

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

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

注意:2つのデリゲートが結合されたとき、または1つが別のデリゲートから削除されたとき、新しいデリゲートの結果を覚えておいてください。既存のデリゲートのコンテンツが変更されていない。 注釈

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

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

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 メソッドが存在しない場合は、コンパイル時エラーが発生した可能性があります。

終了サンプル

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₂,pᵥ=eᵥ}

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

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 が同じ匿名型であるため、最後の行の割り当てが許可されます。

終了サンプル

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

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

«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にすることができません。

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

: unbound_type_name は、type_nametype_argument_list が含まれる unbound_type_namegeneric_dimension_specifier が含まれている点を除き、type_name (§7.8) と非常によく似ています。 注釈

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のインスタンスで表す方法を持つことを望みます。 注釈

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>) の結果は依存します。

終了サンプル

12.8.19 sizeof 演算子

sizeof は、指定された型の変数が占有している8ビットバイトを返します。 sizeof のオペランドとして指定される型は、 unmanaged_type (§8.8) でなければなりません。

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

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

結果
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) (両方のオペランドが整数型または列挙型の場合)。
  • 1 つの整数型または列挙型から別の整数型または列挙型への明示的な数値変換 (§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と同じです。

終了サンプル

: 次のコード例の内容:

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 コンテキストで行われるため、オーバーフローは報告されません。

終了サンプル

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は既定のオーバーフロー チェック コンテキストで評価されます。

終了サンプル

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

:

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

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

終了サンプル

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

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 の結果は、explicitly_typed_defaultの明示的に指定された型の既定値(§9.3)、または default_value_expressionの変換の対象となる型です。

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

  • 参照型
  • 参照型として知られている型パラメーター (§8.2);
  • 次のいずれかの値型: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,;又は
  • あらゆる列挙型。

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
    ;

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_expressionlocal_variable_declaration の初期化式 (§13.6.2) として直接発生する場合、local_variable_type はポインター型 (§23.3) または推論 (var) の場合、 stackalloc_expression の結果は T* 型 (§23.9) のポインターになります。 この場合、 stackalloc_expression は安全でないコードに表示される必要があります。 それ以外の場合、 stackalloc_expression の結果は Span<T>型のインスタンスになります。ここで、 Tunmanaged_typeです。

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

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

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

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

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> に変換されます。 終了サンプル

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_expression は常に構文的にあいまいであり、単純な名前 nameofの呼び出しによってあいまいになります。 互換性上の理由から、名前 nameof の名前検索 (§12.8.4) が成功した場合、呼び出しが有効かどうかに関係なく、式は invocation_expression として扱われます。 それ以外の場合は nameof_expressionです。

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

named_entity がメソッド グループを指定し、type_argument_listを持つ場合、コンパイル時エラーになります。 named_entity_targetdynamic型を持つことはコンパイル時エラーとなります。

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) することです。 終了サンプル

12.8.24 匿名メソッド式

anonymous_method_expression は、匿名関数を定義する 2 つの方法の 1 つです。 これらは、 §12.19でさらに説明されています。

12.9 単項演算子

12.9.1 全般

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

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

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.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項プラス演算子は次のとおりです。

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.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項マイナス演算子は次のとおりです。

  • 整数否定:

    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.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの論理否定演算子が 1 つだけ存在します。

bool operator !(bool x);

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

上記で定義されているリフト解除された事前定義済み論理否定演算子のリフト (§12.4.8) 形式も事前定義されています。

: 接頭論理否定演算子と接尾null許容演算子(§12.8.9)は、同じ語彙トークン(!)で表されますが、異なるものです。 注釈

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

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

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

これらの演算子のそれぞれについて、演算の結果は、 xのビットごとの補数です。

すべての列挙型 E では、次のビットごとの補数演算子が暗黙的に提供されます。

E operator ~(E x);

~xを評価した結果 (X は基になる型 E を持つ列挙型 U の式) は、 (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.9.6) もサポートしています。 x++ または x-- の結果は演算前の x の値ですが、 ++x または --x の結果は演算後の x の値です。 どちらの場合も、 x 自体は演算後に同じ値を持ちます。

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

上記で定義されているリフト解除された定義済みのプレフィックス インクリメント演算子とデクリメント演算子のリフト (§12.4.8) 形式も事前定義されています。

12.9.7 キャスト式

式を明示的に特定の型に変換するには、 cast_expression を使用します。

cast_expression
    : '(' type ')' unary_expression
    ;

(T)E 形式の cast_expression (T is は型で、E は、unary_expression) は、E の型の明示的な変換 (§10.3) を T の型にします。 E から Tへの明示的な変換が存在しない場合は、バインド時エラーが発生します。 それ以外の場合、明示的な変換によって生成される値が結果になります。 E が変数を表している場合でも、結果は常に値として分類されます。

cast_expression の文法は、特定の構文のあいまいさにつながります。

: 式 (x)–y は、 cast_expression (–y から型 xへのキャスト) として解釈することも、 parenthesized_expression (値 x – yを計算する) と組み合わせた additive_expression として解釈することもできます。 終了サンプル

cast_expression のあいまいさを解決するために、次の規則が存在します。かっこで囲まれた 1 つ以上のトークン (§6.4) のシーケンスは、次の少なくとも 1 つが該当する場合にのみ、 cast_expression の開始と見なされます。

  • トークンのシーケンスは型に対して正しい文法ですが、式には適していません。
  • トークンのシーケンスは型の正しい文法であり、閉じかっこのすぐ後のトークンはトークン "~"、トークン "!"、トークン "("、識別子 (§6.4.3)、リテラル (§6.4.5)、またはキーワード (§6.4.4) のいずれか (as および isを除く) です。

上記の "正しい文法" という用語は、トークンのシーケンスが特定の文法の生成に準拠する必要があることを意味します。 具体的には、構成識別子の実際の意味は考慮されません。

: xy が識別子である場合、たとえ x.y が実際に型を示していなくても、x.y は型に関する正しい文法です。 終了サンプル

注意: 非決定規則から、xy が識別子である場合、(x)y(x)(y)、および (x)(-y)cast_expressionですが、たとえ (x)-y が型を識別する場合でも、x はそうではありません。 ただし、x が定義済みの型 (intなど) を識別するキーワードである場合、その 4 つの形式はすべて キャスト式とされています(このようなキーワードを単独で式として扱うことはできないためです)。 注釈

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)
  • 安全でないコンテキストの場合

: await_expression は、非同期でないラムダ式を使用するように構文的に変換されるため、query_expression内のほとんどの場所では発生しません。 注釈

非同期関数内では、 awaitavailable_identifier として使用されませんが、逐語的な識別子 @await 使用できます。 したがって、 await_expressionと識別子を含むさまざまな式の間に構文的なあいまいさはありません。 非同期関数の外部では、 await は通常の識別子として機能します。

await_expression のオペランドは、タスクと呼ばれています。 これは、 await_expression が評価されるときに完了する場合と完了しない場合がある非同期演算を表します。 await 演算子の目的は、待機中のタスクが完了するまで外側の非同期関数の実行を中断し、その結果を取得することです。

12.9.8.2 待機可能式

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 場合、評価は、インターフェイス a を実装 System.Runtime.CompilerServices.ICriticalNotifyCompletion かどうかによって異なります (以下、簡潔にするために 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 の演算では、二項演算子のオーバーロード解決 (§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 は正の有限値です。 z は、最も近い表現可能な値に丸められた x * yの結果です。 結果の大きさが変換先の型に対して大きすぎる場合、 z は無限大です。 丸めのために、 zx もゼロでなくても、 y は 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) を持たない可能性があることを意味します)。

  • 小数の乗算

    decimal operator *(decimal x, decimal y);
    

    結果の値の大きさが10進形式で表現するには大きすぎる場合、System.OverflowExceptionがスローされます。 丸めのため、どちらのオペランドも 0 でない場合でも、結果は 0 になる可能性があります。 四捨五入前の結果のスケールは、2 つのオペランドのスケールの合計になります。 10 進数乗算は、 System.Decimal型の乗算演算子を使用することと同じです。

上で定義されたリフトされていない定義済み乗算演算子のリフトされた形式 (§12.4.8) も事前に定義されています。

12.10.3 除算演算子

フォーム x / y の演算では、二項演算子のオーバーロード解決 (§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 は正の有限値です。 z は、最も近い表現可能な値に丸められた x / 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 になる可能性があります。 丸める前の結果のスケールは、正確な結果と同じ結果を保持する優先スケールに最も近いスケールです。 推奨されるスケールは、x のスケールから yのスケールを引いたものです。

    10 進数除算は、 System.Decimal型の除算演算子を使用することと同じです。

上記で定義されている未リフトの定義済み除算演算子のリフト (§12.4.8) 形式も事前定義されています。

12.10.4 剰余演算子

フォーム x % y の演算では、二項演算子のオーバーロード解決 (§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である場合、System.OverflowException が例外をスローする場合に限り、x / y がスローされます。

  • 浮動小数点の剰余:

    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型の剰余演算子を使用することと同じです。

    注意: これらの規則により、すべての型に対して、結果が左オペランドの反対の符号を持つことはありません。 注釈

上で定義された未リフト状態の定義済み剰余演算子のリフト形式(§12.4.8)もあらかじめ定義されています。

12.10.5 加算演算子

フォーム x + y の演算では、二項演算子のオーバーロード解決 (§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
       }
    }
    

    コメントに表示される出力は、US-English システムでの一般的な結果です。 正確な出力は、実行環境のリージョン設定によって異なります。 文字列連結演算子自体はいずれの場合も同じように動作しますが、実行中に暗黙的に呼び出される ToString メソッドは、地域設定の影響を受ける可能性があります。

    終了サンプル

    文字列連結演算子の結果は、左オペランドの文字とそれに続く右オペランドの文字で構成される 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 はデリゲート型ではないため、演算子 + はそれに対して定義されていません。注記

上で定義した、リフトされていない定義済み加算演算子のリフトされた形式 (§12.4.8) も定義済みです。

12.10.6 減算演算子

フォーム x – y の演算では、二項演算子のオーバーロード解決 (§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
        }
    }
    

    終了サンプル

上記で定義されている未リフトの定義済み減算演算子のリフト (§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の演算では、二項演算子のオーバーロード解決 (§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 の範囲外の上位ビットは破棄され、残りのビットは左にシフトされ、下位の空きビット位置はゼロに設定されます。

  • 右シフト:

    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 が、 int または long の型の場合、 x の下位ビットは破棄され、残りのビットは右にシフトされ、 x が負でない場合は空の上位ビット位置が 0 に設定され、 x が負の場合は 1 に設定されます。

    x が、 uint または ulongの型の場合、 x の下位ビットは破棄され、残りのビットは右にシフトされ、上位の空のビット位置は 0 に設定されます。

定義済みの演算子の場合、シフトするビット数は次のように計算されます。

  • x の型が int または uintの場合、シフト カウントは count の下位 5 ビットによって与えられます。 つまり、シフト カウントは count & 0x1F から計算されます。
  • x の型が long または ulongの場合、シフト カウントは count の下位 6 ビットによって与えられます。 つまり、シフト カウントは count & 0x3F から計算されます。

結果のシフト カウントが 0 の場合、シフト演算子は単に x の値を返します。

シフト演算が原因でオーバーフローが発生することはなく、checked と unchecked のコンテキストで同じ結果が生成されることはありません。

>> 演算子の左オペランドが符号付き整数型の場合、演算子は右に算術シフト を実行します。ここで、オペランドの最上位ビット(符号ビット)の値は、高位の空のビット位置に伝達されます。 >> 演算子の左オペランドが符号なし整数型の場合、演算子は右方向に 論理 シフトを実行します。ここで、上位の空のビット位置は常に 0 に設定されます。 オペランド型から推論された逆の演算を実行するには、明示的なキャストを使用できます。

: xint型の変数である場合、演算 unchecked ((int)((uint)x >> y))xの論理シフト右を実行します。 終了サンプル

上記で定義されている未リフトの定義済みシフト演算子のリフト (§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ほど高い優先順位を持つ必要があります。 注釈

is 演算子については、 §12.12.12 で説明されており、 as 演算子については、 §12.12.13で説明されています。

==!=<><= および >= の演算子は、 比較演算子です。

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 リテラルの場合、オーバーロードの解決は実行されず、演算子が truefalse かに応じて、式は == または != の定数値に評価されます。

定義済みの比較演算子については、次のサブクローズで説明します。 次の表に示すように、定義済みのすべての比較演算子はブール型の結果を返します。

操作 結果
x == y xyに等しい場合は true、 それ以外の場合は false です
x != y xyと等しくない場合は true、それ以外の場合は 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 の場合、結果が false!= 以外のすべての演算子の結果が true になります。 2 つのオペランドの場合、 x != y は常に !(x == y)と同じ結果を生成します。 ただし、一方または両方のオペランドが NaN の場合、<><= の場合、>= 演算子は、反対の演算子の論理否定と同じ結果を生成しません

: xy のいずれかが NaN の場合、 x < yfalseですが、 !(x >= y)trueです。 終了サンプル

どちらのオペランドも 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型の対応するリレーショナル演算子または等値演算子を使用することと同じです。

上記で定義されている未リフトの定義済み小数比較演算子のリフト (§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 は基になる型 E を持つ列挙型 U の式であり、«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) が存在します。
  • 一方のオペランドはリテラル 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に明示的にキャストすることで、常に定義済みの参照型の等値演算子を選択できます。

注釈

: 次の例では、制約のない型パラメーター型の引数が nullされているかどうかを確認します。

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

x == null コンストラクトは、 T が null 非許容値型を表すことができる場合でも許可され、 false が null 非許容値型の場合に T するように結果が単純に定義されます。

終了サンプル

フォーム x == y または x != yの演算で、該当するユーザー定義の operator == または operator != が存在する場合、演算子オーバーロード解決 (§12.4.5) 規則は、定義済みの参照型の等値演算子ではなく、その演算子を選択します。

: 両方のオペランドを明示的に型 objectにキャストすることで、定義済みの参照型等値演算子を選択することが常に可能です。 注釈

: その例

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 つの異なる文字列インスタンスを参照します。 最初の比較では、両方のオペランドが string 型の場合に定義済みの文字列等価演算子 (§12.12.8) が選択されるため、 True が出力されます。 残りの比較のすべては、False を出力します。なぜなら、operator == 型の string のオーバーロードは、いずれかのオペランドがバインド時型の objectを持っている場合には適用されないからです。

上記の手法は、値型では意味を持たないことに注意してください。 例

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

は、False を出力します。これはキャストがボックス化された int 値の 2 つの個別インスタンスへの参照を作成するためです。

終了サンプル

12.12.8 文字列等値演算子

定義済みの文字列等値演算子は次のとおりです。

bool operator ==(string x, string y);
bool operator !=(string x, string y);

次のいずれかが当てはまる場合、2 つの string 値が等しいと見なされます。

  • どちらの値も nullです。
  • どちらの値も、各文字位置に同じ長さと同じ文字を持つ文字列インスタンスへの非null 参照です。

文字列の等値演算子は、文字列参照ではなく文字列値を比較します。 2 つの個別の文字列インスタンスにまったく同じ文字シーケンスが含まれている場合、文字列の値は等しくなりますが、参照は異なります。

: §12.12.7で説明されているように、参照型の等値演算子を使用して、文字列値の代わりに文字列参照を比較できます。 注釈

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 値を等しいと見なすことができない比較を防ぐためのものです。 注釈

12.12.10 null 許容値型と null リテラル間の等価演算子

事前定義された演算子やユーザー定義の演算子が (未リフトまたはリフト形式に) その操作にない場合でも、== および != 演算子は、あるオペランドを 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 タプル等値演算子

タプル等値演算子は、タプル オペランドの要素に構文順にペアワイズで適用されます。

== 演算子または != 演算子の各オペランド xy がタプルまたはタプル型 (§8.3.11) の値として分類される場合、演算子は タプル等価演算子です。

オペランド e がタプルとして分類される場合、 e1...en 要素はタプル式の要素式を評価した結果になります。 それ以外の場合、 e がタプル型の値である場合、要素は t.Item1...t.Itemn となり、 te を評価した結果となります。

タプル等値演算子のオペランド 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 つの形式があります。 ひとつは、is-type operator で、右側に型があります。 もうひとつは、is-pattern operator で、右側にパターンがあります。

12.12.12.1 is-type 演算子

is-type 演算子 は、オブジェクトの実行時の型が特定の型と互換性があるかどうかを確認するために使用されます。 チェックは実行時に実行されます。 E is T が式であり、 ET以外の型である演算 dynamicの結果は、 E が null 以外であり、参照変換、ボックス化変換、ボックス化解除変換、ラップ解除変換によって T 型に正常に変換できるかどうかを示すブール値です。

この演算は、次のように評価されます。

  1. E が匿名関数またはメソッド グループの場合は、コンパイル時エラーが発生します。
  2. Enull リテラルの場合、または E の値が nullである場合、結果は falseとなります。
  3. それ以外の場合:
  4. REのランタイム型にしましょう。
  5. DR から次のように導出されるものとする。
  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) はありません。 注釈

注意: is 演算子は、次のようにコンパイル時の型と変換の観点から理解できます。ここで、 CEのコンパイル時の型です。

  • e のコンパイル時の型が T と同じである場合、または E のコンパイル時の型から T への暗黙的な参照変換 (§10.2.8)、ボックス化変換 (§10.2.9)、ラッピング変換 (§10.6)、または明示的なアンラッピング変換 (§10.6) が存在する場合:
    • C が null 非許容値型の場合、演算の結果は trueとなります。
    • それ以外の場合、演算の結果は E != nullの評価と同じです。
  • それ以外の場合は、 明示的な参照変換 (§10.3.5) またはボックス化解除変換 (§10.3.7) が C から Tに存在する場合、または C または T がオープン型 (§8.4.3) の場合は、上記のランタイム チェックが実行されます。
  • それ以外の場合は、 E から型 T への参照、ボックス化、折り返し、またはラップ解除の変換はできず、演算の結果は falseとなります。 コンパイラは、コンパイル時の型に基づいて最適化を実装できます。

注釈

12.12.12.2 is-pattern 演算子

is-pattern 演算子 は、式 によって計算された値が特定のパターン (§11) と 一致する かどうかを確認するために使用されます。 チェックは実行時に実行されます。 値がパターンと一致する場合、is-pattern 演算子の結果は true になります。それ以外の場合は false です。

フォーム E is Pの式の場合(ここで ET 型の関係式で、 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)、E から T に存在する変換のラッピング (§8.3.12)。
  • 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 は、クラス制約があるため、参照型であることがわかっています。 ただし、 U の型パラメーター H はありません。そのため、 H での as 演算子の使用は許可されません。

終了サンプル

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 が基礎型 E を持つ列挙型 U の式であり、«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 または y のいずれかが true の場合、 x | y の結果は true になります。 それ以外の場合、結果は false です。

x ^ ytrue であり xtrueの場合、または yfalse であり xfalseの場合、y の結果は true になります。 それ以外の場合、結果は false です。 オペランドが bool型の場合、 ^ 演算子は != 演算子と同じ結果を計算します。

12.13.5 null 許容ブール型 & および | 演算子

nullable ブール型 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 に似たセマンティクスが既に提供されています。 注釈

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を返す状態になる可能性があります。 そのような場合、&&|| も短縮しません。 注釈

条件論理演算子のオペランドにコンパイル時の型 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.21で詳しく説明します。

12.14.2 ブール条件論理演算子

&& または || のオペランドが bool型の場合、またはオペランドが該当する operator & または operator |を定義しないが、 boolへの暗黙的な変換を定義する型の場合、演算は次のように処理されます。

  • 演算 x && y は、 x ? y : falseとして評価されます。 つまり、まず x が評価され、 bool型に変換されます。 次に、もし xtrue である場合、 y が評価され、 bool 型に変換され、その結果が演算の結果となります。 それ以外の場合、演算の結果は falseです。
  • 演算 x || y は、 x ? 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)operator false で宣言された T の呼び出しであり、 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)operator true で宣言された T の呼び出しであり、 T.|(x, y) は選択した operator |の呼び出しです。 つまり、 x が最初に評価され、結果に対して operator true が呼び出され、 x が確実に true であるかどうかを判断します。 次に、 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結合式で、もし a が非nullであれば、結果は aとなり、そうでなければ結果は bとなります。 演算は、 anull である場合にのみ、 b を評価します。

null 合体演算子は右連想です。つまり、演算は右から左にグループ化されます。

: a ?? b ?? c 形式の式を a ?? (b ?? c)として評価します。 一般的に、形式 E1 ?? E2 ?? ... ?? EN の式は、null 以外の最初のオペランドを返します。すべてのオペランドが null の場合は null を返します。 終了サンプル

a ?? b 式の型は、オペランドで使用できる暗黙的な変換によって異なります。 優先順に、a ?? b の型は A₀A、または Bです。ただし、A が型を持つ場合は aa の型であり、B が型を持つ場合は bbの型です。また、A₀ がnull許容値型の場合、AA の基になる型、それ以外の場合は 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 が最初に評価されます。 anullされていない場合、a 結果になります。 それ以外の場合、 b が評価され、 A型に変換され、これが結果になります。
  • それ以外の場合、 A が存在し、null 許容値型であり、 b の型が B であり、 A₀ から B への暗黙的な変換が存在する場合、結果の型は B になります。 実行時に、 a が最初に評価されます。 anullではない場合、 aA₀ 型にラップ解除され、 B型に変換され、これが結果になります。 それ以外 の場合、 b が評価され、これが結果となります。
  • それ以外の場合、 b に型 B があり、 a から Bへの暗黙的な変換が存在する場合、結果の型は Bとなります。 実行時に、 a が最初に評価されます。 anullでない場合、 aB型に変換され、これが結果になります。 それ以外 の場合、 b が評価され、これが結果となります。

それ以外の場合、ab に互換性がありません。コンパイル時エラーが発生します。

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オペランドとして使用されます。
  • 式形式 lambda またはメンバーの本文として。

12.17 宣言式

宣言式は、ローカル変数を宣言します。

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

simple_name_ は、単純な名前検索で関連する宣言が見つからなかった場合にも宣言式と見なされます (§12.8.4)。 宣言式として使用する場合、_単純な破棄と呼ばれます。 意味的には var _に相当しますが、より多くの場所で許可されます。

宣言式は次の構文コンテキストでのみ発生します。

  • outargument_value
  • シンプルな代入の左側を構成する単純な破棄 _ として (§12.21.2)。
  • 1 つ以上の再帰的にネストされた tuple_expressiontuple_element として、もっとも外側は、分割代入の左側を囲います。 deconstruction_expression は、宣言式が構文的に存在しない場合でも、この位置で宣言式を生み出します。

注意: 宣言式をかっこで分けることはできません。 注釈

宣言が行われた argument_list 内で参照される declaration_expression で宣言された暗黙的に指定された変数に対するエラーです。

declaration_expression で宣言された変数が、それが発生する分解代入内で参照されることはエラーです。

単純な破棄、または local_variable_typevar 識別子である宣言式は、 暗黙的に型指定された 変数として分類されます。 式には型がなく、ローカル変数の型は次のように構文コンテキストに基づいて推論されます。

  • argument_list では、変数の推論された型は、対応するパラメーターの宣言された型です。
  • 単純な代入の左側として、変数の推論された型は代入の右側の型です。
  • 単純な代入の左側の tuple_expression では、変数の推論された型は、代入の右側 (分解後) の対応するタプル要素の型です。

それ以外の場合、宣言式は 明示的に型指定された 変数 として分類され、式の型と宣言された変数は、 local_variable_typeによって指定されるものとします。

識別子 _ を持つ宣言式は破棄 (§9.2.9.2) であり、変数の名前は導入されません。 _ 以外の識別子を持つ宣言式は、その名前を最も近い外側のローカル変数宣言空間 (§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 の推論された型は、boolの対応する出力パラメーターの型であるため、M1 です。 後続の WriteLine は、i1 および b1 にアクセスでき、囲われた範囲に導入されます。

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 _ が存在しない場合に暗黙的に指定された破棄を行うために _ を使用する方法を示しています。

終了サンプル

12.18 条件演算子

?: は "null 条件演算子" と呼ばれる演算子です。 時には三項演算子とも呼ばれます。

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

条件演算子に throw 式 (§12.16) は、 ref が存在する場合は使用できません。

最初に、フォームの条件式 b ? x : y が条件 bを評価します。 次に、 btrue の場合、 x が評価され、演算の結果になります。 それ以外の場合、 y が評価され、演算の結果になります。 条件式は、xyの両方を評価しません。

条件演算子は右連想です。つまり、演算は右から左にグループ化されます。

: a ? b : c ? d : e 形式の式を a ? b : (c ? d : e)として評価します。 終了サンプル

?: 演算子の最初のオペランドは、暗黙的に boolに変換できる式、または operator trueを実装する型の式である必要があります。 どちらの要件も満たされていない場合は、コンパイル時エラーが発生します。

ref がある場合、

  • 2 つの variable_referenceの型の間には ID 変換が存在し、結果の型はどちらの型でもかまいません。 いずれかの型が dynamic の場合、型推論は dynamic (§8.7) を優先します。 いずれかの型がタプル型 (§8.3.11) の場合、同じ序数位置の要素名が両方のタプルで一致する場合、型推論には要素名が含まれます。
  • 結果は変数参照であり、両方の variable_referenceが書き込み可能な場合は書き込み可能です。

注意: ref が存在する場合、 conditional_expression は変数参照を返します。これは、 = ref 演算子を使用して参照変数に割り当てることができるか、参照/入力/出力パラメーターとして渡すことができます。 注釈

ref が存在しない場合、 ?: 演算子の 2 番目と 3 番目のオペランド (x および y) は、条件式の型を制御します。

  • 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 が評価され、 boolb 値が決定されます。
    • b の型から bool への暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、 bool 値が生成されます。
    • それ以外の場合は、 operator true の型によって定義された b が呼び出され、 bool 値が生成されます。
  • 上記の手順で生成された bool 値が true場合、 x が評価され、結果の変数参照が条件式の結果になります。
  • それ以外の場合、 y が評価され、結果の変数参照が条件式の結果になります。

フォーム b ? x : y の条件式の実行時処理は、次の手順で構成されます。

  • まず、 b が評価され、 boolb 値が決定されます。
    • b の型から bool への暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、 bool 値が生成されます。
    • それ以外の場合は、 operator true の型によって定義された b が呼び出され、 bool 値が生成されます。
  • 上記の手順で生成された bool 値が trueである場合、 x が評価され、条件式の型に変換され、これが条件式の結果になります。
  • それ以外の場合、 y は評価され、条件式の型に変換され、これが条件式の結果になります。

12.19 匿名関数式

12.19.1 全般

匿名関数 は、"インライン" メソッド定義を表す式です。 匿名関数自体には値や型はありませんが、互換性のあるデリゲート型または式ツリー型に変換できます。 匿名関数変換の評価は、変換のターゲット型によって異なります。デリゲート型の場合、匿名関数が定義するメソッドを参照するデリゲート値に変換が評価されます。 式ツリー型の場合、変換はメソッドの構造をオブジェクト構造として表す式ツリーに評価されます。

: 歴史的な理由から、匿名関数には、 lambda_expressionanonymous_method_expressionの 2 種類の構文があります。 ほぼすべての目的で、 lambda_expressionanonymous_method_expressionよりも簡潔で表現力が高く、下位互換性のために言語に残っています。 注釈

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
    ;

anonymous_function_body を認識する際に、null_conditional_invocation_expression 式と 表現 の代替手段の両方が適用可能な場合は、前者を選択しなければなりません。

:ここでの代替手段の重複と優先順位は、説明的な利便性のみを目的としています。文法規則を詳しく説明して、重複を取り除く可能性があります。 ANTLR やその他の文法システムでは、同じ利便性が採用されているため、 anonymous_function_body には指定されたセマンティクスが自動的に適用されます。 注釈

注意: として扱われる場合、結果の種類の Mvoid (§12.8.13) の場合、 x?.M() などの構文形式はエラーになります。 しかし、 null_conditional_invocation_expressionとして扱われる場合、結果の型は voidすることが許可されます。 注釈

例: List<T>.Reverse の結果型は void。 次のコードでは、匿名式の本文は null_conditional_invocation_expressionであるため、エラーではありません。

Action<List<int>> a = x => x?.Reverse();

終了サンプル

=> 演算子は代入 (=) と同じ優先順位を持ち、右結合です。

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

終了サンプル

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) です。 パラメーター リスト (指定されている場合) と共に、匿名メソッド本体は宣言空間 (§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 匿名関数本体

匿名関数の本体 ( または ブロック) は、次の規則に従います。

  • 匿名関数にシグネチャが含まれている場合は、シグネチャで指定されたパラメーターを本文で使用できます。 匿名関数にシグネチャがない場合は、パラメーター (§10.7) を持つデリゲート型または式型に変換できますが、本文でパラメーターにアクセスすることはできません。
  • 最も近くの囲む匿名関数のシグネチャで指定された参照渡しパラメーター(存在する場合)を除き、本文が参照渡しパラメーターにアクセスすることはコンパイル時エラーとなります。
  • 最も近い包囲する匿名関数のシグネチャ (存在する場合) で指定されたパラメーターを除き、関数の本文が ref struct 型のパラメーターにアクセスすることは、コンパイル時のエラーです。
  • this の型が構造体型の場合、本文が thisにアクセスするとコンパイル時エラーが発生します。 これは、アクセスが明示的 (this.xと同様) か暗黙的か (x が構造体のインスタンス メンバーである x の場合と同様) に当てはまります。 この規則は、このようなアクセスを禁止するだけで、メンバールックアップが構造体のメンバーに結果を与えるかどうかには影響しません。
  • 本体は、匿名関数の外部変数 (§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.UnitCount が型 double の値を生成するため、2 番目の Sum メソッドのみが適用されます。 したがって、オーバーロード解決は、その呼び出しの 2 番目の Sum メソッドを選択します。

終了サンプル

12.19.5 匿名関数と動的バインディング

匿名関数は、動的にバインドされた演算の受信者、引数、またはオペランドにすることはできません。

12.19.6 外部変数

12.19.6.1 全般

スコープに lambda_expression または anonymous_method_expression が含まれるローカル変数、値パラメータ、またはパラメータ配列は、匿名関数の 外部変数 と呼ばれます。 クラスのインスタンス関数メンバーでは、この値は値パラメーターと見なされ、関数メンバーに含まれるすべての匿名関数の外部変数です。

12.19.6.2 キャプチャされた外部変数

外部変数が匿名関数によって参照されている場合、外部変数は匿名関数によって キャプチャされた と言われます。 通常、ローカル変数の有効期間は、関連付けられているブロックまたはステートメントの実行に制限されます (§9.2.9.1)。 ただし、キャプチャされた外部変数の有効期間は、少なくとも匿名関数から作成されたデリゲートまたは式ツリーがガベージ コレクションの対象になるまで延長されます。

: この例では

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

終了サンプル

ローカル変数または値パラメーターが匿名関数によってキャプチャされると、ローカル変数またはパラメーターは固定変数 (§23.4) とは見なされなくなりますが、代わりに移動可能変数と見なされます。 ただし、キャプチャされた外部変数は、 fixed ステートメント (§23.7) では使用できないため、キャプチャされた外部変数のアドレスを取得できません。

注意: キャプチャされていない変数とは異なり、キャプチャされたローカル変数を複数の実行スレッドに同時に公開できます。 注釈

12.19.6.3 ローカル変数のインスタンス化

ローカル変数は、実行が変数 スコープに入ったときに インスタンス化 されると見なされます。

: たとえば、次のメソッドが呼び出されると、 x ローカル変数がインスタンス化され、3 回初期化されます。ループの反復処理ごとに 1 回。

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

ただし、 x の宣言をループの外側に移動すると、 xが単一にインスタンス化されます。

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

終了サンプル

キャプチャされない場合は、ローカル変数がインスタンス化される頻度を正確に確認する方法はありません。インスタンス化の有効期間が不整合であるため、各インスタント化で同じストレージの場所を単に使用できます。 ただし、匿名関数がローカル変数をキャプチャすると、インスタンス化の効果が明らかになります。

: その例

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) に最適化することが許可されていることに注意してください (ただし、必須ではありません)。

終了サンプル

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

終了サンプル

匿名関数デリゲートは、キャプチャされた変数の一部を共有しても、別のインスタンスを持つことができます。

例の: たとえば、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

終了サンプル

個別の匿名関数は、外部変数の同じインスタンスをキャプチャできます。

: この例では

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

終了サンプル

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): ascendingbydescendingequalsfromgroupintojoinletonorderbyselect および whereを使用します。

これらの識別子をキーワードと単純名の両方として使用することによって生じる可能性のあるあいまいさを回避するために、これらの識別子は、"@" (§6.4.4) でプレフィックスが付いている場合を除き、クエリ式内の任意の場所でキーワードと見なされます。その場合、識別子と見なされます。 このため、クエリ式は、"from識別子" で始まり、その後に ";"、"=" または "," を除く任意のトークンで始まる任意の式です。

12.20.3 クエリ式の変換

12.20.3.1 全般

C# 言語では、クエリ式の実行セマンティクスは指定されません。 代わりに、クエリ式は、クエリ式パターン (§12.20.4) に準拠するメソッドの呼び出しに変換されます。 具体的には、クエリ式は、 WhereSelectSelectManyJoinGroupJoinOrderByOrderByDescendingThenByThenByDescendingGroupBy、および Castという名前のメソッドの呼び出しに変換されます。 これらのメソッドには、 §12.20.4で説明されているように、特定のシグネチャと戻り値の型が必要です。 これらのメソッドは、クエリ対象のオブジェクトのインスタンス メソッド、またはオブジェクトの外部にある拡張メソッドである場合があります。 これらのメソッドは、クエリの実際の実行を実装します。

クエリ式からメソッド呼び出しへの変換は、型バインディングまたはオーバーロードの解決が実行される前に発生する構文マッピングです。 クエリ式の翻訳後、結果のメソッド呼び出しは通常のメソッド呼び出しとして処理され、コンパイル時エラーが明らかにされる可能性があります。 これらのエラー条件には、存在しないメソッド、間違った型の引数、型推論が失敗するジェネリック メソッドが含まれますが、これらに限定されません。

クエリ式は、さらなる削減が不可能になるまで、次の翻訳を繰り返し適用することによって処理されます。 翻訳はアプリケーションの順序で一覧表示されます。各セクションでは、前のセクションの翻訳が完全に実行されていることを前提としており、使い果たされると、同じクエリ式の処理でセクションが後で再検討されることはありません。

範囲変数への代入を含めるクエリ式のコンパイル時エラーです。または、参照または出力パラメーターの引数として範囲変数を使用します。

特定の翻訳では、*で示された透過的な識別子を持つ範囲変数が挿入されます。 これらは、 §12.19でさらに説明されています。

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

終了サンプル

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

終了サンプル

: 明示的な範囲変数型は、非ジェネリック IEnumerable インターフェイスを実装するコレクションに対してクエリを実行する場合に便利ですが、ジェネリック IEnumerable<T> インターフェイスは使用できません。 上記の例では、顧客が ArrayList型の場合です。 注釈

12.20.3.4 退化したクエリ式

クエリ式の形式

from «x» in «e» select «x»

と翻訳されます

( «e» ) . Select ( «x» => «x» )

: その例

from c in customers
select c

と翻訳されます

(customers).Select(c => c)

終了サンプル

劣化クエリ式とは、ソースの要素を自明的に選択する式です。

: 翻訳の後のフェーズ (§12.20.3.6 および §12.20.3.7) は、他の翻訳手順によって導入された逆生成クエリをソースに置き換えることで削除します。 ただし、クエリ式の結果がソース オブジェクト自体にならないようにすることが重要です。 そうしないと、このようなクエリの結果を返すと、プライベート データ (要素配列など) が呼び出し元に誤って公開される可能性があります。 したがって、この手順では、ソースで Select を明示的に呼び出すことによって、ソース コードで直接記述された退化クエリを保護します。 その後、これらのメソッドがソース オブジェクト自体を返さないことを保証するのは、 Select およびその他のクエリ演算子の実装者にかかっています。 注釈

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

終了サンプル

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 はコンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。

終了サンプル

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 はコンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。

終了サンプル

where 式とその前の from 句:

from «x» in «e»  
where «f»  
...

と翻訳されます

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

select 句が直後に来る join

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

終了サンプル

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 は、コンパイラ生成識別子であり、それ以外の場合は非表示でアクセスできません。

終了サンプル

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)

終了サンプル

次の翻訳では、 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")

終了サンプル

12.20.3.7 グループ条項

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)

終了サンプル

12.20.3.8 透過的な識別子

特定の翻訳は、 が表した*がある範囲変数を挿入します。 透過的な識別子は、クエリ式変換プロセスの中間ステップとしてのみ存在します。

クエリ変換によって透過的な識別子が挿入されると、さらに変換手順を実行すると、透過的な識別子が匿名関数と匿名オブジェクト初期化子に伝達されます。 これらのコンテキストでは、透過的な識別子には次の動作があります。

  • 透過的な識別子が匿名関数のパラメーターとして発生すると、関連付けられた匿名型のメンバーは、匿名関数の本体のスコープ内で自動的に有効になります。
  • 透過的な識別子を持つメンバーがスコープ内にある場合、そのメンバーのメンバーもスコープ内にあります。
  • 透過的な識別子が匿名オブジェクト初期化子のメンバー宣言子として発生すると、透過的な識別子を持つメンバーが導入されます。

上記の変換手順では、透過的な識別子は、複数の範囲変数を 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 は、コンパイラ生成識別子であり、それ以外の場合は非表示でアクセスできません。 終了サンプル

12.20.4 クエリ式パターン

クエリ式パターン は、型がクエリ式をサポートするために実装できるメソッドのパターンを確立します。

パブリック メンバー メソッドとパブリックにアクセス可能な拡張メソッドを次のクラス定義に置き換えることができる場合、ジェネリック型 C<T> はクエリ式パターンをサポートします。 メンバーとアクセス可能な拡張メソッドは、ジェネリック型 C<T>の「形状」と呼ばれます。 ジェネリック型は、パラメーターと戻り値の型の間の適切な関係を示すために使用されますが、非ジェネリック型のパターンも実装できます。

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> の間で推奨される関係により、 ThenBy メソッドと ThenByDescending メソッドは、OrderBy または OrderByDescendingの結果でのみ使用できます。 注釈

注意: GroupByの結果の推奨される形状 。シーケンスのシーケンスであり、各内部シーケンスには追加の Key プロパティがあります。 注釈

: クエリ式は構文マッピングによってメソッド呼び出しに変換されるため、型はクエリ式パターンの一部またはすべてを実装する方法に大きな柔軟性を持ちます。 たとえば、2 つの呼び出し構文が同じであり、匿名関数は両方に変換できるため、メソッドはデリゲートまたは式ツリーを要求できるため、パターンのメソッドをインスタンス メソッドまたは拡張メソッドとして実装できます。 一部のクエリ式パターンのみを実装する型では、型がサポートするメソッドにマップされるクエリ式変換のみがサポートされます。 注釈

: System.Linq 名前空間は、 System.Collections.Generic.IEnumerable<T> インターフェイスを実装するすべての型に対してクエリ式パターンの実装を提供します。 注釈

12.21 代入演算子

12.21.1 全般

代入演算子の 1 つを含め、すべて変数、プロパティ、イベント、またはインデクサー要素に新しい値を割り当てます。 例外 = ref、変数参照 (§9.5) を参照変数 (§9.7) に割り当てます。

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

代入の左オペランドは、変数として分類される式、または = ref、プロパティ アクセス、インデクサー アクセス、イベント アクセス、またはタプルを除く式でなければなりません。 宣言式は左オペランドとして直接許可されませんが、分解代入の評価のステップとして発生する可能性があります。

= 演算子は、 単純代入演算子と呼ばれます。 右側のオペランドの値または値を、左オペランドによって指定された変数、プロパティ、インデクサー要素、またはタプル要素に割り当てます。 単純代入演算子の左オペランドは、イベント アクセスにすることはできません (§15.8.2で説明されている場合を除きます)。 単純代入演算子については、 §12.21.2で説明されています。

= ref 演算子は、 ref 代入演算子と呼ばれます。 右オペランドは、variable_reference (§9.5) でなければならず、それは左オペランドによって指定された参照変数の参照先となります。 ref 代入演算子については、 §12.21.2で説明されています。

= および = ref 演算子以外の代入演算子は、 複合代入演算子と呼ばれます。 これらの演算子は、2 つのオペランドに対して指定された演算を実行し、結果の値を左オペランドによって指定された変数、プロパティ、またはインデクサー要素に割り当てます。 複合代入演算子については、 §12.21.4で説明されています。

左側のオペランドとしてイベント アクセス式を持つ += 演算子と -= 演算子は、 イベント代入演算子と呼ばれます。 左側のオペランドとしてイベント アクセスを持つ他の代入演算子は有効ではありません。 イベント代入演算子については、 §12.21.4で説明されています。

代入演算子は右結合性を持っています。つまり、操作は右から左にグループ化されます。

: a = b = c 形式の式を a = (b = c)として評価します。 終了サンプル

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 の型は、 xy への割り当ての型であり、次のように再帰的に決定されます。

  • 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を持ちます。
  • それ以外の場合、割り当てが有効ではなく、バインド時エラーが発生します。

型が x = y のフォーム T の単純な割り当ての実行時処理は、次の再帰的な手順で構成される、 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 が、アリティ (x1, ..., xn) があるタプル n として分類される場合:
    • y は、n の要素を使用してタプル式 e に分解されます。
    • 結果のタプル t は、暗黙的なタプル変換を使用して eT に変換することによって作成されます。
    • 左から右に、各 xi に対して順番に xit.Itemi への割り当てが行われますが、その際、xi は再評価されません。
    • t は、割り当ての結果として生成されます。

注意: x のコンパイル時の型が dynamic で、 y のコンパイル時の型から dynamicへの暗黙的な変換がある場合、実行時の解決は必要ありません。 注釈

: 配列の共分散規則 (§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

最後の代入は、System.ArrayTypeMismatchException への参照が、ArrayList 要素に格納できないことが原因で string[] をスローする原因となります。

注釈

struct_type で宣言されたプロパティまたはインデクサーが代入のターゲットである場合、プロパティまたはインデクサー アクセスに関連付けられているインスタンス式は変数として分類されます。 インスタンス式が値として分類されると、バインド時エラーが発生します。

: §12.8.7のため、フィールドにも同じルールが適用されます。 注釈

: 宣言を指定します:

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;

pr は変数であるため、 p.Xp.Yr.A、および r.B への割り当ては許可されます。 ただし、この例では

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

r.Ar.B は変数ではないため、代入はすべて無効です。

終了サンプル

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
}

終了サンプル

注意: = ref 演算子を使用してコードを読み取るとき、オペランドの一部として ref 部分を読み取りたくなる可能性があります。 これは、オペランドが条件付き ?: 式である場合に特に混乱します。 たとえば、 ref int a = ref b ? ref x : ref y; を読むときは、 = ref が演算子、 b ? ref x : ref y が右オペランド ref int a = ref (b ? ref x : ref y); であると読み取ることが重要です。 重要なのは、式 ref b が一件ステートメントの一部として見えても、 ステートメントの一部ではないことです。 注釈

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 = x «op» yとして評価されます。ただし、 x は 1 回だけ評価されます。
  • それ以外の場合、選択した演算子が定義済みの演算子である場合、選択した演算子の戻り値の型が x の型に明示的に変換可能であり、 yx の型に暗黙的に変換できる場合、または演算子がシフト演算子である場合、演算は x = (T)(x «op» y)として評価されます。ここで、 Txの型です。 ただし、 x は 1 回だけ評価されます。
  • それ以外の場合、複合代入は無効であり、バインド時エラーが発生します。

「一度だけ評価される」という用語は、 x «op» yの評価において、 x の構成式の結果が一時的に保存され、 xへの割り当てを実行するときに再利用されることを意味します。

: 代入 A()[B()] += C()では、 Aint[]を返すメソッドであり、 B および Cintを返すメソッドであり、メソッドは ABCの順序で 1 回だけ呼び出されます。 終了サンプル

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

上記の 2 番目の規則では、特定のコンテキストで x «op»= yx = (T)(x «op» y) として評価できます。 この規則は、左オペランドが sbytebyteshortushort、または charの型である場合に、定義済みの演算子を複合演算子として使用できるように存在します。 両方の引数がこれらの型のいずれかである場合でも、定義済みの演算子は、 §12.4.7.3で説明されているように、 int型の結果を生成します。 したがって、キャストがないと、結果を左オペランドに割り当てられなくなります。

定義済みの演算子に対するルールの直感的な効果は、 x «op»= yx «op» y の両方が許可されている場合に x = 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

各エラーの直感的な理由は、対応する単純な割り当てもエラーだったということです。

終了サンプル

: これは、複合代入演算がリフトされた演算子をサポートすることも意味します。 複合代入 x «op»= yx = x «op» y または x = (T)(x «op» y)として評価されるため、評価の規則では、リフトされた演算子が暗黙的にカバーされます。 注釈

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 値または次のいずれかの型を持つ必要があります。

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string;
  • 列挙型、または
  • 参照型の既定値の式 (§12.8.21) です。

定数式では、次のコンストラクトのみが許可されます。

  • リテラル (null リテラルを含む)。
  • クラスおよび構造体型の const メンバーへの参照。
  • 列挙型のメンバーへの参照。
  • ローカル定数への参照。
  • 括弧で括られた部分式、それ自体が定数式です。
  • キャスト式。
  • checked および unchecked 式。
  • nameof 式。
  • 定義済みの +-! (論理否定)、および単項演算子 ~
  • 定義済みの +-*/%<<>>&|^&&||==!=<><=、および >= 二項演算子。
  • ?: 条件演算子。
  • ! null-forgiving 演算子 (§12.8.9)。
  • sizeof 式は、アンマネージド型が §23.6.9 で指定された型のひとつであり、sizeof は、定数値を返します。
  • 型が上記のいずれかである場合のデフォルト値式。

定数式では、次の変換を使用できます。

  • ID 変換
  • 数値変換
  • 列挙の変換
  • 定数式の変換
  • 変換のソースが null 値に評価される定数式である場合において、暗黙的および明示的な参照変換が行われます。

:null 以外の値のボックス化、ボックス化解除、暗黙的な参照変換などのその他の変換は、定数式では使用できません。 注釈

: 次のコード例の内容:

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

ボックス化変換が必要であるため、 i の初期化はエラーです。 str の初期化は、null 以外の値からの暗黙的な参照変換が必要であるため、エラーです。

終了サンプル

式が上記の要件を満たすたびに、式はコンパイル時に評価されます。 これは、式が非定数コンストラクトを含む大きな式の部分式である場合でも当てはまります。

定数式のコンパイル時評価では、非定数式の実行時評価と同じ規則が使用されます。ただし、実行時の評価で例外がスローされる場合、コンパイル時の評価によってコンパイル時エラーが発生する点が異なります。

定数式が unchecked コンテキストに明示的に配置されていない限り、式のコンパイル時の評価中に整数型の算術演算および変換で発生するオーバーフローは、常にコンパイル時エラーを引き起こします (§12.8.20)。

定数式は、以下に示すコンテキストで必要です。これは、 constant_expressionを使用して文法で示されます。 これらのコンテキストでは、コンパイル時に式を完全に評価できない場合、コンパイル時エラーが発生します。

  • 定数宣言 (§15.4)
  • 列挙メンバー宣言 (§19.4)
  • パラメーター リストの既定の引数 (§15.6.2)
  • case ステートメントの switch ラベル (§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として分類されます。

boolean_expressionE は、次のように、 bool型の値を生成できる必要があります。

  • E が bool に暗黙的に変換できる場合は、実行時に暗黙的な変換が適用されます。
  • それ以外の場合は、単項演算子のオーバーロード解決 (§12.4.4) を使用して、 Eでの operator true の固有の最適な実装を見つけ、実行時にその実装が適用されます。
  • このような演算子が見つからない場合は、バインド時エラーが発生します。