12 式
12.1 全般
式は、演算子とオペランドのシーケンスです。 この句は、構文、オペランドと演算子の評価順序、および式の意味を定義します。
12.2 式の分類
12.2.1 全般
式の結果は、次のいずれかとして分類されます。
- 値。 すべての値には、型が関連付けられています。
- 変数。 特に指定しない限り、変数は明示的に型指定され、関連付けられた型 (つまり、変数の宣言された型) を持ちます。 暗黙的に型指定された変数には、関連付けられた型がありません。
- null リテラル。 この分類を持つ式は、参照型または null 許容値型に暗黙的に変換できます。
- 匿名関数。 この分類を持つ式は、互換性のあるデリゲート型または式ツリー型に暗黙的に変換できます。
- タプル。 すべてのタプルには固定数の要素があり、それぞれに式とオプションのタプル要素名が付きます。
- プロパティ へのアクセス。 すべてのプロパティ アクセスには、関連付けられた型 (つまり、プロパティの型) があります。 さらに、プロパティ アクセスには、インスタンス式が関連付けられている場合があります。 インスタンス プロパティ アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、
this
で表されるインスタンスになります (§12.8.14)。 - インデクサー アクセス。 すべてのインデクサー アクセスには、インデクサーの要素型という、関連付けられた型があります。 さらに、インデクサー アクセスには、関連付けられたインスタンス式と、関連付けられた引数リストがあります。 インデクサー アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果が
this
(§12.8.14) で表されるインスタンスになり、引数リストを評価した結果が呼び出しのパラメーター リストになります。 - Nothing。 これは、式が戻り値の型が
void
のメソッドの呼び出しである場合に発生します。 何も分類されない式は、 statement_expression (§13.7) または lambda_expression の本文 (§12.19) のコンテキストでのみ有効です。
大きな式の部分式として発生する式の場合は、注意が必要です。結果は次のいずれかとして分類することもできます。
- 名前空間。 この分類を持つ式は、 member_access の左側 (§12.8.7) としてのみ表示できます。 その他のコンテキストでは、名前空間として分類された式によってコンパイル時エラーが発生します。
- 型。 この分類を持つ式は、 member_access の左側 (§12.8.7) としてのみ表示できます。 その他のコンテキストでは、型として分類された式によってコンパイル時エラーが発生します。
- メソッド グループ。メンバー参照 (§12.5) に起因するオーバーロードされたメソッドのセットです。 メソッド グループには、関連付けられたインスタンス式と、関連付けられた型引数リストが含まれる場合があります。 インスタンス メソッドが呼び出されると、インスタンス式を評価した結果は、
this
で表されるインスタンスになります (§12.8.14)。 メソッド グループは、 invocation_expression (§12.8.10) または delegate_creation_expression (§12.8.17.6) で許可され、互換性のあるデリゲート型 (§10.8) に暗黙的に変換できます。 その他のコンテキストでは、メソッド グループとして分類された式によってコンパイル時エラーが発生します。 - イベント アクセス。 すべてのイベント アクセスには、関連付けられた型 (つまり、イベントの型) があります。 さらに、イベント アクセスには、インスタンス式が関連付けられている場合があります。 イベント アクセスは、
+=
演算子と-=
演算子 (§12.21.5) の左オペランドとして表示される場合があります。 その他のコンテキストでは、イベント アクセスとして分類された式によってコンパイル時エラーが発生します。 インスタンス イベント アクセスのアクセサーが呼び出されると、インスタンス式を評価した結果は、this
で表されるインスタンスになります (§12.8.14)。 - throw 式は、式内で例外をスローするために使用できるいくつかのコンテキストです。 スロー式は、任意の型への暗黙的な変換によって変換できます。
プロパティ アクセスまたはインデクサー アクセスは、get アクセサーまたは set アクセサーの呼び出しを実行することで、常に値として再分類されます。 特定のアクセサーは、プロパティまたはインデクサー アクセスのコンテキストによって決定されます。アクセスが割り当てのターゲットである場合、set アクセサーが呼び出されて新しい値が割り当てられます (§12.21.2)。 それ以外の場合は、get アクセサーが呼び出されて現在の値が取得されます (§12.2.2)。
instance アクセサーは、インスタンスのプロパティ アクセス、インスタンスでのイベント アクセス、またはインデクサー アクセスです。
12.2.2 式の値
式を含むコンストラクトのほとんどは、最終的に式が 値を表す必要があります。 このような場合、実際の式が名前空間、型、メソッド グループ、または何も示していない場合、コンパイル時エラーが発生します。 ただし、式がプロパティ アクセス、インデクサー アクセス、または変数を表す場合、プロパティ、インデクサー、または変数の値は暗黙的に置き換えられます。
- 変数の値は、変数によって識別されるストレージの場所に現在格納されている値にすぎません。 変数は、値を取得する前に確実に代入 (§9.4) と見なされるか、それ以外の場合はコンパイル時エラーが発生します。
- プロパティ アクセス式の値は、プロパティの get アクセサーを呼び出すことによって取得されます。 プロパティに get アクセサーがない場合は、コンパイル時エラーが発生します。 それ以外の場合は、関数メンバー呼び出し (§12.6.6) が実行され、呼び出しの結果がプロパティ アクセス式の値になります。
- インデクサー アクセス式の値は、インデクサーの get アクセサーを呼び出すことによって取得されます。 インデクサーに get アクセサーがない場合は、コンパイル時エラーが発生します。 それ以外の場合は、インデクサー アクセス式に関連付けられた引数リストを使用して関数メンバー呼び出し (§12.6.6) が実行され、呼び出しの結果がインデクサー アクセス式の値になります。
- タプル式の値は、タプル式の型に暗黙的なタプル変換 (§10.2.13) を適用することによって取得されます。 型を持たないタプル式の値を取得するとエラーになります。
12.3 静的および動的バインディング
12.3.1 全般
バインド は、式 (引数、オペランド、受信者) の型または値に基づいて、操作が何を参照するのかを決定するプロセスです。 たとえば、メソッド呼び出しのバインドは、受信側と引数の型に基づいて決定されます。 演算子のバインドは、そのオペランドの型に基づいて決定されます。
C# では、操作のバインドは通常、その部分式のコンパイル時の型に基づいてコンパイル時に決定されます。 同様に、式にエラーが含まれている場合は、エラーが検出され、コンパイラによって報告されます。 このアプローチは、 静的バインディングと呼ばれます。
ただし、式が 動的な式である場合 (つまり、型が dynamic
) これは、参加するバインディングがコンパイル時の型ではなく実行時の型に基づいている必要があることを示します。 したがって、このような操作のバインドは、プログラムの実行中に操作が実行されるまで遅延されます。 これは、 ダイナミック バインディングと呼ばれます。
操作が動的にバインドされている場合、コンパイラによってチェックはほとんどまたはまったく実行されません。 代わりに、実行時バインディングが失敗した場合、エラーは実行時に例外として報告されます。
C# での次の操作はバインディングの対象となります。
- メンバー アクセス:
e.M
- メソッドの呼び出し:
e.M(e₁,...,eᵥ)
- デリゲート呼び出し:
e(e₁,...,eᵥ)
- 要素アクセス:
e[e₁,...,eᵥ]
- オブジェクトの作成: 新規
C(e₁,...,eᵥ)
- オーバーロードされた単項演算子:
+
、-
、!
(論理否定のみ)、~
、++
、--
、true
、false
- オーバーロードされた二項演算子:
+
、-
、*
、/
、%
、&
、&&
|
、||
、??
、^
、<<
、>>
、==
、!=
、>
、<
、>=
、<=
- 代入演算子:
=
、= ref
、+=
、-=
、*=
、/=
、%=
、&=
、|=
^=
、<<=
、>>=
- 暗黙的な型変換と明示的な型変換
動的な式が関係しない場合、C# では既定で静的バインディングが使用されます。つまり、選択プロセスでコンパイル時の部分式の型が使用されます。 ただし、上記の操作の部分式の 1 つが動的な式である場合、操作は代わりに動的にバインドされます。
メソッド呼び出しが動的にバインドされ、受信側を含むパラメーターのいずれかが入力パラメーターである場合、コンパイル時エラーになります。
12.3.2 バインディング時間
静的バインディングはコンパイル時に行われますが、動的バインディングは実行時に行われます。 次のサブクラウスでは、 binding-time という用語は、バインドが行われるタイミングに応じて、コンパイル時または実行時を指します。
例: 静的バインディングと動的バインディングとバインディング時間の概念を次に示します。
object o = 5; dynamic d = 5; Console.WriteLine(5); // static binding to Console.WriteLine(int) Console.WriteLine(o); // static binding to Console.WriteLine(object) Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)
最初の 2 つの呼び出しは静的にバインドされます。
Console.WriteLine
のオーバーロードは、引数のコンパイル時の型に基づいて選択されます。 したがって、バインディング時間は compile-time です。3 番目の呼び出しは動的にバインドされます。
Console.WriteLine
のオーバーロードは、その引数の実行時の型に基づいて選択されます。 これは、引数が動的な式であり、コンパイル時の型が動的であるために発生します。 したがって、3 番目の呼び出しのバインディング時間は run-time です。end の例
12.3.3 動的バインディング
このサブクラスは有益です。
動的バインディングを使用すると、C# プログラムは動的オブジェクト(C# 型システムの通常のルールに従わないオブジェクト)と対話できます。 動的オブジェクトは、異なる型システムを持つ他のプログラミング言語のオブジェクトである場合もあれば、さまざまな操作に対して独自のバインド セマンティクスを実装するようにプログラムによって設定されるオブジェクトである場合もあります。
動的オブジェクトが独自のセマンティクスを実装するメカニズムは、実装定義です。 指定されたインターフェイス (再び実装定義) は、特別なセマンティクスがあることを C# ランタイムに通知するために、動的オブジェクトによって実装されます。 したがって、動的オブジェクトに対する操作が動的にバインドされるたびに、この仕様で指定されている C# ではなく、独自のバインド セマンティクスが引き継がれる。
動的バインディングの目的は動的オブジェクトとの相互運用を許可することですが、C# では、動的かどうかに関係なく、すべてのオブジェクトに対して動的バインディングを許可します。 これにより、動的オブジェクトに対する操作の結果自体が動的オブジェクトではない可能性がありますが、コンパイル時にプログラマにはまだ不明な型である可能性があるため、動的オブジェクトをよりスムーズに統合できます。 また、動的バインディングは、関係するオブジェクトが動的オブジェクトでない場合でも、エラーが発生しやすいリフレクション ベースのコードを排除するのに役立ちます。
12.3.4 部分式の種類
演算が静的にバインドされている場合、部分式の型 (受信者、引数、インデックス、オペランドなど) は、常にその式のコンパイル時の型と見なされます。
操作が動的にバインドされている場合、部分式の型は、部分式のコンパイル時の型に応じて異なる方法で決定されます。
- コンパイル時型 dynamic の部分式は、式が実行時に評価する実際の値の型を持つと見なされます
- コンパイル時の型が型パラメーターである部分式は、実行時に型パラメーターがバインドされる型を持つと見なされます
- それ以外の場合、部分式はコンパイル時型と見なされます。
12.4 演算子
12.4.1 全般
"式" は "オペランド" と "演算子" で構成されます。 式の演算子は、オペランドに適用する演算を表します。
例: 演算子の例としては、
+
、-
、*
、/
、new
などがあります。 オペランドの例としては、リテラル、フィールド、ローカル変数、式などがあります。 end の例
演算子には次の 3 種類があります。
- 単項演算子。 単項演算子は 1 つのオペランドを受け取り、プレフィックス表記 (
–x
など) または後置表記 (x++
など) を使用します。 - 二項演算子。 二項演算子は 2 つのオペランドを受け取り、すべて infix 表記 (
x + y
など) を使用します。 - 三項演算子。 1 つの三項演算子 (
?:
) のみが存在します。3 つのオペランドを受け取り、インフィックス表記 (c ? x : y
) を使用します。
式内の演算子の評価順序は、演算子の 前 と 関連付け によって決まります (§12.4.2)。
式のオペランドは、左から右に評価されます。
例:
F(i) + G(i++) * H(i)
では、メソッドF
はi
の古い値を使用して呼び出され、メソッドG
はi
の古い値で呼び出され、最後にメソッドH
は新しい値 i で呼び出されます。 これは、演算子の優先順位とは別であり、関連性がありません。 end の例
特定の演算子はオーバーロードできます。 演算子オーバーロード (§12.4.3) を使用すると、一方または両方のオペランドがユーザー定義クラスまたは構造体型である操作に対して、ユーザー定義演算子の実装を指定できます。
12.4.2 演算子の優先順位と結合性
式に複数の演算子が含まれている場合、演算子の "優先順位" によって、個々の演算子を評価する順序が制御されます。
注: たとえば、
*
演算子の方がバイナリ+
演算子よりも優先順位が高いため、式x + y * z
はx + (y * z)
として評価されます。 end note
演算子の優先順位は、関連付けられている文法プロダクションの定義によって決定されます。
注: たとえば、additive_expressionは、
+
演算子または-
演算子で区切られた一連のmultiplicative_expressionで構成されるため、+
演算子と-
演算子は、*
、/
、および%
演算子よりも優先順位が低くなります。 end note
注: 次の表は、すべての演算子を優先順位の高い順にまとめたものです。
サブクラウス カテゴリ 演算子 §12.8 プライマリ x.y
x?.y
f(x)
a[x]
a?[x]
x++
x--
x!
new
typeof
default
checked
unchecked
delegate
stackalloc
§12.9 単項 +
-
!x
~
++x
--x
(T)x
await x
§12.10 乗法 *
/
%
§12.10 加法 +
-
§12.11 Shift <<
>>
§12.12 関係式と型検査 <
>
<=
>=
is
as
§12.12 等式 ==
!=
§12.13 論理 AND &
§12.13 論理 XOR ^
§12.13 論理和 \|
§12.14 条件 AND &&
§12.14 条件 OR \|\|
§12.15 および §12.16 null 結合とスロー式 ??
throw x
§12.18 条件 ?:
§12.21 および §12.19 代入とラムダ式 =
= ref
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
\|=
=>
end note
1 つのオペランドが同じ優先順位を持つ 2 つの演算子の間で発生した場合、演算子の結合性によって演算が実行される順序が決定されます。
- 代入演算子と null 合体演算子を除き、すべての二項演算子は 左から右に行われます。
例:
x + y + z
は(x + y) + z
として評価されます。 end の例 - 代入演算子、null 合体演算子、および条件演算子 (
?:
) は 右から連想。つまり、操作は右から左に実行されます。例:
x = y = z
はx = (y = z)
として評価されます。 end の例
優先順位と結合性は、かっこを使用して制御することができます。
例:
x + y * z
最初にy
をz
乗算し、結果をx
に追加しますが、(x + y) * z
最初にx
とy
を追加し、結果をz
乗算します。 end の例
12.4.3 演算子のオーバーロード
単項演算子と二項演算子には、すべて定義済みの実装があります。 さらに、クラスと構造体に演算子宣言 (§15.10) を含めることで、ユーザー定義の実装を導入できます。 ユーザー定義演算子の実装は、定義済みの演算子の実装よりも常に優先されます。 §12.4.4 および §12.4.5 で説明されているように、該当するユーザー定義演算子の実装が存在しない場合にのみ、定義済みの演算子の実装が考慮されます。
読み込み可能な単項演算子は次のとおりです。
+ - !
(論理否定のみ) ~ ++ -- true false
注:
true
とfalse
は式で明示的に使用されません (したがって、 §12.4.2 の優先順位テーブルには含まれません)、複数の式コンテキストで呼び出されるため、演算子と見なされます。s: ブール式 (§12.24) と条件 (§12.18) と条件論理演算子 (§12.14) を含む式。 end note
注: null 許容演算子 (後置
!
、 §12.8.9) はオーバーロード可能な演算子ではありません。 end note
読み込み可能な二項演算子は次のとおりです。
+ - * / % & | ^ << >> == != > < <= >=
オーバーロードできるのは、上記の演算子だけです。 特に、メンバー アクセス、メソッド呼び出し、または =
、 &&
、 ||
、 ??
、 ?:
、 =>
、 checked
、 unchecked
、 new
、 typeof
、 default
、 as
、および is
演算子をオーバーロードすることはできません。
二項演算子がオーバーロードされると、対応する複合代入演算子 (存在する場合) も暗黙的にオーバーロードされます。
例: 演算子
*
のオーバーロードは、演算子*=
のオーバーロードでもあります。 これについては、 §12.21 で詳しく説明します。 end の例
代入演算子自体 (=)
オーバーロードすることはできません。 代入では常に、変数への値の単純な格納が実行されます (§12.21.2)。
(T)x
などのキャスト操作は、ユーザー定義の変換 (§10.5) を提供することによってオーバーロードされます。
注: ユーザー定義の変換は、
is
演算子またはas
演算子の動作には影響しません。 end note
a[x]
などの要素アクセスは、オーバーロード可能な演算子とは見なされません。 代わりに、ユーザー定義のインデックス作成はインデクサー (§15.9) によってサポートされます。
式では、演算子は演算子表記を使用して参照され、宣言では関数型表記を使用して演算子が参照されます。 次の表は、単項演算子と二項演算子の演算子と関数表記の関係を示しています。 最初のエントリでは、«op» はオーバーロード可能な単項プレフィックス演算子を表します。 2 番目のエントリでは、«op» は単項後置 ++
および --
演算子を表します。 3 番目のエントリでは、«op» はオーバーロード可能なバイナリ演算子を表します。
注:
++
演算子と--
演算子のオーバーロードの例については、 §15.10.2 を参照してください。 end note
演算子表記 | 機能表記 |
---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
ユーザー定義の演算子宣言では、常に少なくとも 1 つのパラメーターが、演算子宣言を含むクラスまたは構造体型である必要があります。
注: したがって、ユーザー定義演算子が定義済みの演算子と同じシグネチャを持つことはできません。 end note
ユーザー定義演算子の宣言では、演算子の構文、優先順位、または結合規則を変更できません。
例:
/
演算子は常に二項演算子であり、常に §12.4.2 で指定された優先順位レベルを持ち、常に左から連想されます。 end の例
注: ユーザー定義演算子が必要な計算を実行することは可能ですが、直感的に予想される結果以外の結果を生成する実装は強くお勧めしません。 たとえば、演算子
==
の実装では、2 つのオペランドの等価性を比較し、適切なbool
結果を返す必要があります。 end note
§12.9 から §12.21 までの個々の演算子の説明では演算子の定義済みの実装と、各演算子に適用される追加の規則を指定します。 この説明では、 非項演算子オーバーロードの解決、 binary 演算子のオーバーロードの解決、 数値の昇格、およびリフトされた演算子の定義を使用します。これらの定義は、次のサブクラスにあります。
12.4.4 単項演算子オーバーロードの解決
フォーム «op» x
または x «op»
の演算。«op» はオーバーロード可能な単項演算子で、 x
は X
型の式であり、次のように処理されます。
- 操作
operator «op»(x)
のX
によって提供される候補ユーザー定義演算子のセットは、§12.4.6 の規則を使用して決定されます。 - 候補のユーザー定義演算子のセットが空でない場合、これは操作の候補演算子のセットになります。 それ以外の場合、定義済みのバイナリ
operator «op»
実装 (リフトされたフォームを含む) が、操作の候補演算子のセットになります。 特定の演算子の定義済みの実装は、演算子の説明で指定されます。 列挙型またはデリゲート型によって提供される定義済みの演算子は、バインディング時型 (null 許容型の場合は基になる型) が列挙型またはデリゲート型である場合にのみ、このセットに含まれます。 - §12.6.4 のオーバーロード解決規則は、引数リスト
(x)
に関して最適な演算子を選択する候補演算子のセットに適用され、この演算子はオーバーロード解決プロセスの結果になります。 オーバーロードの解決で最適な演算子を 1 つ選択できない場合は、バインド時エラーが発生します。
12.4.5 二項演算子のオーバーロードの解決
フォーム x «op» y
の演算。«op» はオーバーロード可能な二項演算子で、 x
は X
型の式で、 y
は Y
型の式であり、次のように処理されます。
- 操作
operator «op»(x, y)
のX
とY
によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、X
によって提供される候補演算子と、Y
によって提供される候補演算子の和集合で構成され、それぞれ §12.4.6 の規則を使用して決定。 結合セットの場合、候補は次のようにマージされます。X
とY
が ID 変換可能な場合、またはX
とY
が共通の基本型から派生している場合、共有候補演算子は結合セット内で 1 回だけ発生します。X
とY
の間に ID 変換がある場合、Y
によって提供される演算子«op»Y
は、X
によって提供される«op»X
と同じ戻り値の型を持ち、«op»Y
のオペランド型は、«op»X
の対応するオペランド型への ID 変換を持つ場合、セット内で«op»X
のみ発生します。
- 候補のユーザー定義演算子のセットが空でない場合、これは操作の候補演算子のセットになります。 それ以外の場合、定義済みのバイナリ
operator «op»
実装 (リフトされたフォームを含む) が、操作の候補演算子のセットになります。 特定の演算子の定義済みの実装は、演算子の説明で指定されます。 定義済みの列挙型およびデリゲート演算子の場合、考慮される演算子は、いずれかのオペランドのバインド時型である列挙型またはデリゲート型によって提供される演算子のみです。 - §12.6.4 のオーバーロード解決規則は、引数リスト
(x, y)
に関して最適な演算子を選択する候補演算子のセットに適用され、この演算子はオーバーロード解決プロセスの結果になります。 オーバーロードの解決で最適な演算子を 1 つ選択できない場合は、バインド時エラーが発生します。
12.4.6 ユーザー定義演算子候補
型T
と操作operator «op»(A)
(«op» はオーバーロード可能な演算子、A
は引数リスト) を指定すると、演算子«op»(A)
のT
によって提供される候補ユーザー定義演算子のセットは次のように決定されます。
T₀
型を決定します。T
が null 許容値型の場合、T₀
は基になる型です。それ以外の場合、T₀
はT
と等しくなります。- 引数リスト
A
に対して少なくとも 1 つの演算子 (§12.6.4.2) が適用される場合、T₀
のすべてのoperator «op»
宣言と、そのような演算子のセットは、T₀
の該当するすべての演算子で構成されます。 - それ以外の場合、
T₀
がobject
されている場合、候補の演算子のセットは空になります。 - それ以外の場合、
T₀
によって提供される候補演算子のセットは、T₀
の直接基底クラスによって提供される候補演算子のセット、またはT₀
が型パラメーターの場合はT₀
の有効な基底クラスです。
12.4.7 数値の昇格
12.4.7.1 全般
このサブクラスは有益です。
§12.4.7 とそのサブクラウスは、次の効果の組み合わせの概要です。
- 暗黙的な数値変換の規則 (§10.2.3)
- より良い変換のための規則 (§12.6.4.7);
- 使用可能な算術演算子 (§12.10)、リレーショナル (§12.12)、整数論理 (§12.13.2) 演算子。
数値の昇格は、定義済みの単項および二項数値演算子のオペランドの特定の暗黙的な変換を自動的に実行することで構成されます。 数値の昇格は、明確なメカニズムではなく、定義済みの演算子にオーバーロード解決を適用する効果です。 数値の昇格は、特にユーザー定義演算子の評価には影響しませんが、同様の効果を示すためにユーザー定義演算子を実装できます。
数値の昇格の例として、バイナリ *
演算子の定義済みの実装を考えてみましょう。
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
この一連の演算子にオーバーロード解決規則 (§12.6.4) を適用すると、オペランド型から暗黙的な変換が存在する最初の演算子を選択する効果があります。
例:
b
がbyte
で、s
がshort
である操作b * s
の場合、オーバーロードの解決では最適な演算子としてoperator *(int, int)
が選択されます。 したがって、b
とs
がint
に変換され、結果の型がint
されます。 同様に、i
がint
でd
がdouble
である操作i * d
の場合、overload
解決では最適な演算子としてoperator *(double, double)
が選択されます。 end の例
情報テキストの末尾。
12.4.7.2 単項数値の昇格
このサブクラスは有益です。
単項数値の昇格は、定義済みの +
、 -
、および ~
単項演算子のオペランドに対して行われます。 単項数値の昇格は、単に型 sbyte
、 byte
、 short
、 ushort
、または char
のオペランドを型 int
に変換することで構成されます。 さらに、単項 - 演算子の場合、単項数値昇格は、 uint
型のオペランドを型 long
に変換します。
情報テキストの末尾。
12.4.7.3 バイナリ数値の上位変換
このサブクラスは有益です。
2 項数値の昇格は、定義済みの +
、 -
、 *
、 /
、 %
、 &
、 |
、 ^
、 ==
、 !=
、 >
、 <
、 >=
、および <=
二項演算子のオペランドに対して行われます。 二項数値昇格は、両方のオペランドを共通の型に暗黙的に変換します。非関係演算子の場合は、演算の結果型にもなります。 バイナリ数値の昇格は、次の規則をここに表示される順序で適用することで構成されます。
- いずれかのオペランドが
decimal
型の場合、もう一方のオペランドは型decimal
に変換されます。または、もう一方のオペランドがfloat
型またはdouble
の場合、バインド時エラーが発生します。 - それ以外の場合、いずれかのオペランドが
double
型の場合、もう一方のオペランドはdouble
型に変換されます。 - それ以外の場合、いずれかのオペランドが
float
型の場合、もう一方のオペランドはfloat
型に変換されます。 - それ以外の場合、いずれかのオペランドが
ulong
型の場合、もう一方のオペランドは型ulong
に変換されます。または、もう一方のオペランドがtype sbyte
、short
、int
、またはlong
の場合、バインド時エラーが発生します。 - それ以外の場合、いずれかのオペランドが
long
型の場合、もう一方のオペランドはlong
型に変換されます。 - それ以外の場合、いずれかのオペランドが
uint
型で、もう一方のオペランドが型sbyte
、short
、またはint
の場合、両方のオペランドが型long
に変換されます。 - それ以外の場合、いずれかのオペランドが
uint
型の場合、もう一方のオペランドはuint
型に変換されます。 - それ以外の場合、両方のオペランドが型
int
に変換されます。
注: 最初の規則では、
decimal
型とdouble
型とfloat
型を混在させる操作は許可されません。 この規則は、decimal
型とdouble
型とfloat
型の間に暗黙的な変換が存在しないという事実に従います。 end note
注: また、もう一方のオペランドが符号付き整数型の場合、オペランドが
ulong
型であることはできません。 その理由は、符号付き整数型だけでなく、ulong
の全範囲を表すことができる整数型が存在しないためです。 end note
上記のどちらの場合も、キャスト式を使用して、一方のオペランドを、もう一方のオペランドと互換性のある型に明示的に変換できます。
例: 次のコード内
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);
decimal
にdouble
を乗算できないため、バインド時エラーが発生します。 エラーは、次のように 2 番目のオペランドを明示的にdecimal
に変換することで解決されます。decimal AddPercent(decimal x, double percent) => x * (decimal)(1.0 + percent / 100.0);
end の例
情報テキストの末尾。
12.4.8 リフト演算子
リフト演算子 、null 非許容値型に対して動作する定義済みの演算子とユーザー定義演算子を、それらの型の null 許容形式でも使用できます。 リフト演算子は、次に示すように、特定の要件を満たす定義済みおよびユーザー定義の演算子から構築されます。
- 単項演算子
+
、++
、-
、--
、!
(論理否定)、および~
の場合、オペランドと結果の型の両方が null 非許容値型の場合、リフトされた形式の演算子が存在します。 リフトされたフォームは、オペランドと結果の型に 1 つの?
修飾子を追加することによって構築されます。 オペランドがnull
されている場合、リフトされた演算子はnull
値を生成します。 それ以外の場合、リフトされた演算子はオペランドのラップを解除し、基になる演算子を適用して結果をラップします。 - 二項演算子
+
、-
、*
、/
、%
、&
、|
、^
、<<
、および>>
の場合、オペランドと結果の型がすべて null 非許容値型の場合、リフトされた形式の演算子が存在します。 リフトされたフォームは、各オペランドと結果の型に 1 つの?
修飾子を追加することによって構築されます。 リフトされた演算子は、一方または両方のオペランドがnull
場合にnull
値を生成します (例外は、§12.13.5 で説明されているように、bool?
型の&
演算子と|
演算子)。 それ以外の場合、リフトされた演算子はオペランドのラップを解除し、基になる演算子を適用して、結果をラップします。 - 等値演算子
==
および!=
の場合、オペランド型が null 非許容値型であり、結果の型がbool
場合は、リフトされた形式の演算子が存在します。 リフトされた形式は、各オペランド型に単一の?
修飾子を追加することによって構築されます。 リフトされた演算子では、2 つのnull
値が等しく、null
値がnull
以外の値と等しくないと見なされます。 両方のオペランドがnull
でない場合、リフトされた演算子はオペランドをラップ解除し、基になる演算子を適用してbool
結果を生成します。 - 関係演算子
<
、>
、<=
、および>=
の場合、オペランドの型が null 非許容値型であり、結果の型がbool
場合は、リフトされた形式の演算子が存在します。 リフトされた形式は、各オペランド型に単一の?
修飾子を追加することによって構築されます。 リフトされた演算子は、一方または両方のオペランドがnull
場合にfalse
値を生成します。 それ以外の場合、リフトされた演算子はオペランドをラップ解除し、基になる演算子を適用してbool
結果を生成します。
12.5 メンバー参照
12.5.1 全般
メンバー参照とは、型のコンテキストにおける名前の意味が決定されるプロセスです。 メンバー参照は、式の simple_name (§12.8.4) または member_access (§12.8.7) の評価の一環として行うことができます。 invocation_expressionのprimary_expressionとしてsimple_nameまたはmember_accessが発生した場合 (§12.8.10.2)、メンバーはに割り当てられている。
メンバーがメソッドまたはイベントである場合、またはデリゲート型 (§20) または型 dynamic
(§8.2.4) の定数、フィールド、またはプロパティである場合、メンバーは 使用可能であると言われます。
メンバー参照では、メンバーの名前だけでなく、メンバーが持つ型パラメーターの数と、メンバーにアクセスできるかどうかを考慮します。 メンバー参照の目的上、ジェネリック メソッドと入れ子になったジェネリック型には、それぞれの宣言で示される型パラメーターの数が含まれており、他のすべてのメンバーには 0 個の型パラメーターがあります。
型T
の型引数K
持つ名前N
のメンバー検索は、次のように処理されます。
- まず、
N
という名前のアクセス可能なメンバーのセットが決定されます。T
が型パラメーターの場合、セットは、T
のプライマリ制約またはセカンダリ制約 (§15.2.5) として指定された各型でN
という名前のアクセス可能なメンバーのセットと、object
のN
という名前のアクセス可能なメンバーのセットの和集合です。- それ以外の場合、セットは、継承されたメンバーと
object
内のN
という名前のアクセス可能なメンバーを含め、T
のN
という名前のすべてのアクセス可能な (§7.5) メンバーで構成されます。T
が構築された型の場合、メンバーのセットは、§15.3.3 で説明されているように型引数を置き換えることによって取得されます。override
修飾子を含むメンバーは、セットから除外されます。
- 次に、
K
が 0 の場合、型パラメーターを含む宣言を持つ入れ子になった型はすべて削除されます。K
が 0 でない場合は、型パラメーターの数が異なるメンバーがすべて削除されます。K
が 0 の場合、型推論プロセス (§12.6.3) が型引数を推論できる可能性があるため、型パラメーターを持つメソッドは削除されません。 - 次に、メンバーが呼び出されると、呼び出し不可能なすべてのメンバーがセットから削除されます。
- 次に、他のメンバーによって非表示になっているメンバーがセットから削除されます。
S
はメンバーM
が宣言されている型であるセット内のすべてのメンバーS.M
に対して、次の規則が適用されます。M
が定数、フィールド、プロパティ、イベント、または列挙メンバーの場合、基本型のS
で宣言されているすべてのメンバーがセットから削除されます。M
が型宣言の場合、基本型のS
で宣言されているすべての非型がセットから削除され、基本型のS
で宣言M
と同じ数の型パラメーターを持つすべての型宣言がセットから削除されます。M
がメソッドの場合、基本型のS
で宣言されているすべての非メソッド メンバーがセットから削除されます。
- 次に、クラス メンバーによって非表示になっているインターフェイス メンバーがセットから削除されます。 この手順は、
T
が型パラメーターであり、T
にobject
以外の有効な基底クラスと空でない有効なインターフェイス セット (§15.2.5) の両方がある場合にのみ有効です。S
はメンバーM
が宣言されている型であるセット内のすべてのメンバーS.M
に対して、S
がobject
以外のクラス宣言である場合に、次の規則が適用されます。M
が定数、フィールド、プロパティ、イベント、列挙型メンバー、または型宣言の場合、インターフェイス宣言で宣言されているすべてのメンバーがセットから削除されます。M
がメソッドの場合、インターフェイス宣言で宣言されているすべての非メソッド メンバーがセットから削除され、インターフェイス宣言で宣言されたM
と同じシグネチャを持つすべてのメソッドがセットから削除されます。
- 最後に、非表示のメンバーを削除すると、検索の結果が決定されます。
- セットがメソッドではない 1 つのメンバーで構成されている場合、このメンバーは参照の結果になります。
- それ以外の場合、セットにメソッドのみが含まれている場合、このメソッドのグループは参照の結果になります。
- それ以外の場合、参照があいまいになり、バインド時エラーが発生します。
型パラメーターとインターフェイス以外の型のメンバー参照と、厳密に単一継承であるインターフェイスのメンバー参照 (継承チェーン内の各インターフェイスには、0 個または 1 つの直接基本インターフェイスがある) の場合、参照規則の効果は、派生メンバーが同じ名前またはシグネチャを持つ基本メンバーを非表示にするだけです。 このような単一継承参照があいまいになることはありません。 多重継承インターフェイスでのメンバー参照によって生じる可能性があるあいまいさは、 §18.4.6 で説明されています。
注: このフェーズでは、1 種類のあいまいさのみが考慮されます。 メンバー参照の結果がメソッド グループに含まれる場合は、§12.6.4.1、§12.6.6.1 で説明されているように、あいまいさのためにメソッド グループの使用がさらに失敗する可能性があります。 end note
12.5.2 基本型
メンバー参照の目的で、 T
型は次の基本型を持つと見なされます。
T
がobject
またはdynamic
の場合、T
は基本型を持っていません。T
がenum_typeの場合、T
の基本型は、System.Enum
、System.ValueType
、およびobject
のクラス型です。T
がstruct_typeの場合、T
の基本型はSystem.ValueType
およびobject
クラス型です。注: nullable_value_type は struct_type です (§8.3.1)。 end note
T
がclass_typeの場合、T
の基本型は、クラス型object
を含むT
の基底クラスです。T
がinterface_typeの場合、T
の基本型はT
の基本インターフェイスであり、クラス型object
。T
がarray_typeの場合、T
の基本型はSystem.Array
およびobject
クラス型です。T
がdelegate_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 つのカテゴリの関数メンバーを含むコンストラクトで行われる処理をまとめたものです。 表では、
e
、x
、y
、およびvalue
は、変数または値として分類される式を示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
が書き込み専用の場合、コンパイル時エラーが発生します。P
がstatic
されていない場合、インスタンス式はthis
。P = value
包含クラスまたは構造体のプロパティ P
の set アクセサーは、引数リスト(value)
を使用して呼び出されます。P
が読み取り専用の場合、コンパイル時エラーが発生します。P
がstatic
されていない場合、インスタンス式はthis
。T.P
クラスまたは構造体 T
内のプロパティP
の get アクセサーが呼び出されます。 コンパイル時エラーは、P
がstatic
されていない場合、またはP
が書き込み専用の場合に発生します。T.P = value
クラスまたは構造体 T
内のプロパティP
の set アクセサーは、引数リスト(value)
を使用して呼び出されます。P
がstatic
されていない場合、またはP
が読み取り専用の場合、コンパイル時エラーが発生します。e.P
E
の型によって指定されたクラス、構造体、またはインターフェイス内のプロパティP
の get アクセサーは、インスタンス式e
で呼び出されます。 バインド時エラーは、P
がstatic
されている場合、またはP
が書き込み専用の場合に発生します。e.P = value
E
の型によって指定されたクラス、構造体、またはインターフェイスのプロパティP
の set アクセサーは、インスタンス式e
と引数リスト(value)
で呼び出されます。 バインド時エラーは、P
がstatic
場合、またはP
が読み取り専用の場合に発生します。イベント アクセス E += value
包含クラスまたは構造体内のイベント E
の追加アクセサーが呼び出されます。E
がstatic
されていない場合、インスタンス式はthis
。E -= value
包含クラスまたは構造体内のイベント E
の remove アクセサーが呼び出されます。E
がstatic
されていない場合、インスタンス式はthis
。T.E += value
クラスまたは構造体 T
内のイベントE
の追加アクセサーが呼び出されます。E
がstatic
されていない場合、バインド時エラーが発生します。T.E -= value
クラスまたは構造体 T
内のイベントE
の remove アクセサーが呼び出されます。E
がstatic
されていない場合、バインド時エラーが発生します。e.E += value
E
の型によって指定されたクラス、構造体、またはインターフェイス内のイベントE
の追加アクセサーは、インスタンス式e
で呼び出されます。E
がstatic
されると、バインド時エラーが発生します。e.E -= value
E
の型によって指定されたクラス、構造体、またはインターフェイス内のイベントE
の remove アクセサーは、インスタンス式e
で呼び出されます。E
がstatic
されると、バインド時エラーが発生します。インデクサーへのアクセス e[x, y]
オーバーロードの解決は、 e
の型によって指定されたクラス、構造体、またはインターフェイスで最適なインデクサーを選択するために適用されます。 インデクサーの get アクセサーは、インスタンス式e
で呼び出され、引数リスト(x, y)
。 インデクサーが書き込み専用の場合、バインド時エラーが発生します。e[x, y] = value
オーバーロードの解決は、 e
の型によって指定されたクラス、構造体、またはインターフェイスで最適なインデクサーを選択するために適用されます。 インデクサーの set アクセサーは、インスタンス式e
と引数リスト(x, y, value)
で呼び出されます。 インデクサーが読み取り専用の場合、バインド時エラーが発生します。演算子の呼び出し -x
オーバーロードの解決は、 x
の型によって指定されたクラスまたは構造体で最適な単項演算子を選択するために適用されます。 選択した演算子は、引数リスト(x)
で呼び出されます。x + y
オーバーロードの解決は、 x
とy
の型によって指定されるクラスまたは構造体で最適な二項演算子を選択するために適用されます。 選択した演算子は、引数リスト(x, y)
で呼び出されます。インスタンス コンストラクターの呼び出し new T(x, y)
オーバーロードの解決は、クラスまたは構造体の T
で最適なインスタンス コンストラクターを選択するために適用されます。 インスタンス コンストラクターは、引数リスト(x, y)
を使用して呼び出されます。end note
12.6.2 引数リスト
12.6.2.1 全般
すべての関数メンバーとデリゲートの呼び出しには、関数メンバーのパラメーターの実際の値または変数参照を提供する引数リストが含まれています。 関数メンバー呼び出しの引数リストを指定するための構文は、関数メンバー のカテゴリによって異なります。
- インスタンス コンストラクター、メソッド、インデクサー、デリゲートの場合、引数は次に示すように、 argument_listとして指定されます。 インデクサーの場合、set アクセサーを呼び出すとき、引数リストには、代入演算子の右オペランドとして指定された式も追加で含まれます。
注: この追加引数は、set アクセサーの呼び出し中にだけ、オーバーロードの解決には使用されません。 end note
- プロパティの場合、get アクセサーを呼び出すときに引数リストが空になり、set アクセサーを呼び出すときに代入演算子の右オペランドとして指定された式で構成されます。
- イベントの場合、引数リストは、
+=
または-=
演算子の右オペランドとして指定された式で構成されます。 - ユーザー定義演算子の場合、引数リストは単項演算子の単一オペランドまたは二項演算子の 2 つのオペランドで構成されます。
プロパティ (§15.7) とイベント (§15.8) の引数は常に値パラメーター (§15.6.2.2) として渡されます。 ユーザー定義演算子 (§15.10) の引数は、常に値パラメーター (§15.6.2.2) または入力パラメーター (§9.2.8) として渡されます。 インデクサー (§15.9) の引数は、常に値パラメーター (§15.6.2.2)、入力パラメーター (§9.2.8)、またはパラメーター配列 (§15.6.2.4) として渡されます。 関数メンバーのこれらのカテゴリでは、出力パラメーターと参照パラメーターはサポートされていません。
インスタンス コンストラクター、メソッド、インデクサー、またはデリゲート呼び出しの引数は、 argument_listとして指定されます。
argument_list
: argument (',' argument)*
;
argument
: argument_name? argument_value
;
argument_name
: identifier ':'
;
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' variable_reference
;
argument_listは、コンマで区切られた 1 つ以上のargumentで構成されます。 各引数は、省略可能な argument_name の後に argument_valueで構成されます。 argument_nameを持つargumentは名付き引数と呼ばれますがargument_nameのないargumentは位置引数です。
argument_valueには、次のいずれかの形式を使用できます。
- 式。引数が値パラメーターとして渡されるか、入力パラメーターに変換された後、(§12.6.4.2 によって決定され、§12.6.2.3 で説明されているように渡。
- キーワード
in
、引数が入力パラメーター (§15.6.2.3.2) として渡されることを示すvariable_reference (§9.5) が続きます。 変数を入力パラメーターとして渡す前に、変数を確実に割り当てる必要があります (§9.4)。 - キーワード
ref
続けて variable_reference (§9.5)、引数が参照パラメーターとして渡されることを示します (§15.6.2.3.3)。 変数を参照パラメーターとして渡す前に、変数を確実に割り当てる必要があります (§9.4)。 - キーワード
out
続けて variable_reference (§9.5)、引数が出力パラメーターとして渡されることを示します (§15.6.2.3.4)。 変数が出力パラメーターとして渡される関数メンバー呼び出しの後、変数は確実に割り当てられていると見なされます (§9.4)。
このフォームは、引数 value、input、reference、または output のパラメーター受け渡しモードを決定します。 ただし、前述のように、値渡しモードの引数は、入力パッシング モードで 1 つに変換される可能性があります。
揮発性フィールド (§15.5.4) を入力、出力、または参照パラメーターとして渡すと、呼び出されたメソッドによってフィールドが揮発性として扱われないため、警告が発生します。
12.6.2.2 対応するパラメーター
引数リスト内の引数ごとに、呼び出される関数メンバーまたはデリゲートに対応するパラメーターが必要です。
次に示すパラメーター リストは、次のように決定されます。
- クラスで定義されている仮想メソッドとインデクサーの場合、パラメーター リストは、レシーバーの静的型から開始し、その基底クラスを検索するときに検出された関数メンバーの最初の宣言またはオーバーライドから選択されます。
- 部分メソッドの場合、定義する部分メソッド宣言のパラメーター リストが使用されます。
- 他のすべての関数メンバーとデリゲートには、使用されるパラメーター リストが 1 つだけあります。
引数またはパラメーターの位置は、引数リストまたはパラメーター リストの前にある引数またはパラメーターの数として定義されます。
関数メンバー引数の対応するパラメーターは、次のように設定されます。
- インスタンス コンストラクター、メソッド、インデクサー、デリゲートの argument_list 内の引数:
- パラメーターがパラメーター リスト内の同じ位置に存在する位置引数は、パラメーターがパラメーター配列であり、関数メンバーがその展開された形式で呼び出されない限り、そのパラメーターに対応します。
- 展開された形式で呼び出されたパラメーター配列を持つ関数メンバーの位置引数。パラメーター リスト内のパラメーター配列の位置以降に発生し、パラメーター配列内の要素に対応します。
- 名前付き引数は、パラメーター リスト内の同じ名前のパラメーターに対応します。
- インデクサーの場合、set アクセサーを呼び出すとき、代入演算子の右オペランドとして指定された式は、set アクセサー宣言の暗黙的な
value
パラメーターに対応します。
- プロパティの場合、get アクセサーを呼び出すときに引数はありません。 set アクセサーを呼び出すとき、代入演算子の右オペランドとして指定された式は、set アクセサー宣言の暗黙的な値パラメーターに対応します。
- ユーザー定義の単項演算子 (変換を含む) の場合、単一オペランドは演算子宣言の単一パラメーターに対応します。
- ユーザー定義の二項演算子の場合、左オペランドは最初のパラメーターに対応し、右側のオペランドは演算子宣言の 2 番目のパラメーターに対応します。
- 名前のない引数は、パラメーター配列に対応する位置外の名前付き引数または名前付き引数の後にある場合、パラメーターに対応しません。
注: これにより、
M(c: false, valueB);
によってvoid M(bool a = true, bool b = true, bool c = true);
が呼び出されなくなります。 最初の引数は範囲外で使用されます (引数は最初の位置で使用されますが、c
という名前のパラメーターは 3 番目の位置にあります)、次の引数に名前を付ける必要があります。 つまり、末尾以外の名前付き引数は、名前と位置が同じ対応するパラメーターを検索する場合にのみ許可されます。 end note
12.6.2.3 引数リストの実行時評価
関数メンバー呼び出し (§12.6.6) の実行時に、引数リストの式または変数参照は、次のように左から右に順番に評価されます。
パラメーターの受け渡しモードが value の場合、値引数の場合
引数式が評価され、対応するパラメーター型への暗黙的な変換 (§10.2) が実行されます。 結果の値は、関数メンバー呼び出しの value パラメーターの初期値になります。
それ以外の場合は、パラメーターの受け渡しモードが入力されます。 引数が変数参照であり、引数の型とパラメーターの型の間に ID 変換 (§10.2.2) が存在する場合、結果の格納場所は関数メンバー呼び出しのパラメーターによって表される格納場所になります。 それ以外の場合は、対応するパラメーターと同じ型でストレージの場所が作成されます。 引数式が評価され、対応するパラメーター型への暗黙的な変換 (§10.2) が実行されます。 結果の値は、そのストレージの場所に格納されます。 そのストレージの場所は、関数メンバー呼び出しの入力パラメーターによって表されます。
例: 次の宣言とメソッド呼び出しが考えられます。
static void M1(in int p1) { ... } int i = 10; M1(i); // i is passed as an input argument M1(i + 5); // transformed to a temporary input argument
M1(i)
メソッド呼び出しでは、i
自体が入力引数として渡されます。これは、変数として分類され、入力パラメーターと同じ型int
があるためです。M1(i + 5)
メソッド呼び出しでは、名前のないint
変数が作成され、引数の値で初期化された後、入力引数として渡されます。 §12.6.4.2 および §12.6.4.4 を参照。end の例
入力、出力、または参照引数の場合、変数参照が評価され、結果の格納場所が関数メンバー呼び出しのパラメーターによって表される格納場所になります。 入力または参照引数の場合、変数はメソッド呼び出しの時点で確実に割り当てられます。 変数参照が出力引数として指定されている場合、または reference_typeの配列要素である場合は、実行時チェックが実行され、配列の要素型がパラメーターの型と同じであることを確認します。 このチェックが失敗すると、
System.ArrayTypeMismatchException
がスローされます。
注: この実行時チェックは、配列の共変性 (§17.6) が原因で必要です。 end note
例: 次のコード内
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }
F
の 2 番目の呼び出しでは、b
の実際の要素型がstring
され、object
されていないため、System.ArrayTypeMismatchException
がスローされます。end の例
メソッド、インデクサー、およびインスタンス コンストラクターは、その右端のパラメーターをパラメーター配列として宣言できます (§15.6.2.4)。 このような関数メンバーは、該当する (§12.6.4.2) に応じて、通常の形式または展開された形式で呼び出されます。
- パラメーター配列を持つ関数メンバーが通常の形式で呼び出されると、パラメーター配列に指定される引数は、パラメーター配列型に暗黙的に変換可能な単一の式 (§10.2) になります。 この場合、パラメーター配列は値パラメーターとまったく同じように動作します。
- パラメーター配列を持つ関数メンバーが展開形式で呼び出されると、呼び出しでは、パラメーター配列に対して 0 個以上の位置引数を指定する必要があります。各引数は、パラメーター配列の要素型に暗黙的に変換可能な式 (§10.2) です。 この場合、呼び出しは引数の数に対応する長さのパラメーター配列型のインスタンスを作成し、指定された引数値を使用して配列インスタンスの要素を初期化し、新しく作成された配列インスタンスを実際の引数として使用します。
引数リストの式は、常にテキスト順に評価されます。
例: したがって、例
class Test { static void F(int x, int y = -1, int z = -2) => Console.WriteLine($"x = {x}, y = {y}, z = {z}"); static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
この例では、次のように出力されます。
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
end の例
パラメーター配列を持つ関数メンバーが展開された形式で少なくとも 1 つの展開された引数で呼び出されると、展開された引数の周囲に配列初期化子 (§12.8.17.5) を持つ配列作成式が挿入されたかのように呼び出しが処理されます。 パラメーター配列の引数がない場合は、空の配列が渡されます。渡された参照が新しく割り当てられた空の配列であるか、既存の空の配列であるかは指定されていません。
例: 宣言を指定する
void F(int x, int y, params object[] args);
メソッドの拡張形式の次の呼び出し
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);
に正確に対応する
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });
end の例
対応する省略可能なパラメーターを持つ関数メンバーから引数を省略すると、関数メンバー宣言の既定の引数が暗黙的に渡されます。 (これには、前述のように、ストレージの場所の作成が含まれる場合があります)。
注: これらは常に定数であるため、その評価は残りの引数の評価には影響しません。 end note
12.6.3 型推論
12.6.3.1 全般
型引数を指定せずにジェネリック メソッドを呼び出すと、 型推論 プロセスは呼び出しの型引数の推論を試みます。 型推論が存在すると、ジェネリック メソッドを呼び出すためにより便利な構文を使用でき、プログラマは冗長な型情報を指定しないようにすることができます。
例:
class Chooser { static Random rand = new Random(); public static T Choose<T>(T first, T second) => rand.Next(2) == 0 ? first : second; } class A { static void M() { int i = Chooser.Choose(5, 213); // Calls Choose<int> string s = Chooser.Choose("apple", "banana"); // Calls Choose<string> } }
型推論により、型引数
int
およびstring
は、引数からメソッドに対して決定されます。end の例
型推論は、メソッド呼び出しのバインド時処理 (§12.8.10.2) の一部として発生し、呼び出しのオーバーロード解決ステップの前に行われます。 メソッドの呼び出しで特定のメソッド グループが指定されていて、メソッド呼び出しの一部として型引数が指定されていない場合、メソッド グループ内の各ジェネリック メソッドに型推論が適用されます。 型の推論が成功した場合、推論された型引数を使用して、後続のオーバーロード解決のための引数の型が決定されます。 オーバーロード解決でジェネリック メソッドを呼び出すメソッドとして選択した場合、推論された型引数が呼び出しの型引数として使用されます。 特定のメソッドの型推論が失敗した場合、そのメソッドはオーバーロードの解決に関与しません。 型推論自体が失敗しても、バインド時エラーは発生しません。 ただし、オーバーロードの解決で該当するメソッドが見つからない場合、バインド時エラーが発生することがよくあります。
指定された各引数がメソッド内の 1 つのパラメーター (§12.6.2.2) に完全に対応していない場合、または対応する引数のない省略可能でないパラメーターがある場合、推論はすぐに失敗します。 それ以外の場合は、ジェネリック メソッドに次のシグネチャがあるとします。
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
フォームM(E₁ ...Eₓ)
メソッド呼び出しでは、型推論のタスクは、呼び出しM<S₁...Sᵥ>(E₁...Eₓ)
が有効になるようにX₁...Xᵥ
各型パラメーターに対してS₁...Sᵥ
一意の型引数を検索することです。
型推論のプロセスをアルゴリズムとして以下に説明します。 準拠コンパイラは、すべてのケースで同じ結果に達した場合に、代替アプローチを使用して実装できます。
推論のプロセス中に、各型パラメーターXᵢ
は、特定の型Sᵢ
に修正されるか関連付けられた一連のbounds.各境界は、何らかの型のT
です。 最初に、各型変数 Xᵢ
は、空の境界セットで固定解除されます。
型の推論は段階的に行われます。 各フェーズでは、前のフェーズの結果に基づいて、より多くの型変数の型引数を推論しようとします。 最初のフェーズでは境界のいくつかの初期推論が行われますが、2 番目のフェーズでは型変数が特定の型に修正され、さらに境界が推論されます。 2 番目のフェーズは、何度も繰り返す必要がある場合があります。
注: 型推論は、メソッド グループの変換 (§12.6.3.14) や一連の式の最も一般的な型 (§12.6.3.15) の検索など、他のコンテキストでも使用されます。 end note
12.6.3.2 第 1 フェーズ
各メソッド引数 Eᵢ
:
Eᵢ
が匿名関数の場合、explicit パラメーター型推論 (§12.6.3.8) がfromEᵢ
Tᵢ
- それ以外の場合、
Eᵢ
に型U
があり、対応するパラメーターが値パラメーター (§15.6.2.2) の場合、lower バインド推論 (§12.6.3.10) はU
Tᵢ
からされます。 - それ以外の場合、
Eᵢ
に型U
があり、対応するパラメーターが参照パラメーター (§15.6.2.3.3) または出力パラメーター (§15.6. 2.3.4) の後に、 例の推論 (§12.6.3.9) が されますU
toTᵢ
。 - それ以外の場合、
Eᵢ
に型U
があり、対応するパラメーターが入力パラメーター (§15.6.2.3.2) であり、Eᵢ
が入力引数である場合は、exact 推論 (§12.6.3.9) がU
から されますTᵢ
。 - それ以外の場合、
Eᵢ
に型U
があり、対応するパラメーターが入力パラメーター (§15.6.2.3.2) の場合、lower バインド推論 (§12.6.3.10) がfromU
Tᵢ
になります。 - それ以外の場合、この引数の推論は行われません。
12.6.3.3 第2フェーズ
2 番目のフェーズは次のように進みます。
- すべての修正されていない型変数
Xᵢ
(§12.6.3.6)Xₑ
はすべて固定されます (§12.6.3.12)。 - このような型変数が存在しない場合、 unfixed 型変数
Xᵢ
はすべて 修正され 次のすべてが保持されます。- 1 つ以上の型変数
Xₑ
依存しているXᵢ
Xᵢ
に空でない境界のセットがある
- 1 つ以上の型変数
- このような型変数が存在せず、まだ 未修正 型変数がある場合、型の推論は失敗します。
- それ以外の場合、型変数 それ以上修正されていない場合 型の推論は成功します。
- それ以外の場合は、対応するパラメーター型
Eᵢ
Tᵢ
を持つすべての引数に対して出力型 (§12.6.3.5) に未修正型Xₑ
変数が含まれますが、input 型 (§12.6.3.4 は含まれません)。 出力型推論 (§12.6.3.7) は、fromEᵢ
toTᵢ
に行われます。 その後、2 番目のフェーズが繰り返されます。
12.6.3.4 入力の種類
E
がメソッド グループまたは暗黙的に型指定された匿名関数で、T
がデリゲート型または式ツリー型の場合、T
のすべてのパラメーター型は input 型E
with typeT
。
12.6.3.5 出力の種類
E
がメソッド グループまたは匿名関数で、T
がデリゲート型または式ツリー型の場合、T
の戻り値の型は output 型E
with typeT
。
12.6.3.6 依存
固定されていない型変数Xᵢ
型Tᵥ
Xₑ
を持つ引数Eᵥ
が発生した場合にXₑ
固定されていない型変数 型がTᵥ
でXᵢ
を持つEᵥ
のinput型のは、Tᵥ
型のEᵥ
の出力型で発生します。
Xₑ
depends onXᵢ
Xₑ
に直接依存する場合Xᵢ
またはXᵢ
に直接依存している場合Xᵥ
Xᵥ
に依存しない場合Xₑ
。 したがって、"depends on" は、"depends directly on" の推移的ですが、再帰的な閉鎖ではありません。
12.6.3.7 出力型の推論
出力型推論は、次のように型 T E
式されます。
E
が推論された戻り値の型U
(§12.6.3.13) を持つ匿名関数であり、T
がデリゲート型または戻り値の型がTₓ
式ツリー型の場合、 その後、lower バインド推論 (§12.6.3.10) がfromU
toTₓ
に作成されます。- それ以外の場合、
E
がメソッド グループであり、T
がパラメーター型T₁...Tᵥ
および戻り値の型Tₓ
を持つデリゲート型または式ツリー型であり、戻り値の型T₁...Tᵥ
E
のオーバーロード解決により、戻り値の型がU
された単一のメソッドが生成された場合、lower バインド推論はU
からされますTₓ
。 - それ以外の場合、
E
が型U
を持つ式である場合は、 lower バインド推論 が fromU
toT
になります。 - それ以外の場合、推論は行われません。
12.6.3.8 明示的なパラメーター型の推論
explicit パラメーター型推論は、次のように型T
式E
されます。
E
がパラメーター型U₁...Uᵥ
明示的に型指定された匿名関数で、T
がパラメーター型がV₁...Vᵥ
デリゲート型または式ツリー型である場合、各Uᵢ
exact 推論 (§12.6.3.9) は、対応するVᵢ
Uᵢ
からされます。
12.6.3.9 正確な推論
例の推論 from型U
to型V
は次のように行われます。
V
が unfixed の 1 つである場合Xᵢ
U
はXᵢ
の正確な境界のセットに追加されます。- それ以外の場合、
V₁...Vₑ
とU₁...Uₑ
のセットは、次のいずれかのケースが適用されるかどうかを確認することによって決定されます。V
は配列型V₁[...]
で、U
は同じランクの配列型U₁[...]
ですV
は型V₁?
で、U
は型ですU₁
V
は構築型C<V₁...Vₑ>
で、U
は構築された型ですC<U₁...Uₑ>
これらのケースのいずれかが該当する場合は、対応するVᵢ
に対する各Uᵢ
からの推定が行われます。
- それ以外の場合、推論は行われません。
12.6.3.10 下限推論
型U
からにバインドされた推論型V
は次のように行われます。
V
が unfixed の 1 つである場合Xᵢ
Xᵢ
の一連の下限にU
が追加されます。- それ以外の場合、
V
が型V₁?
で、U
が型U₁?
場合、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
は構築されたclass
、struct
、interface
、またはdelegate
型のC<V₁...Vₑ>
であり、U
(または、U
が型parameter
の場合、その有効な基底クラスまたはその有効なインターフェイス セットのメンバー) が (直接または間接的に) 同一であるかinherits
、(直接または間接的に)C<U₁...Uₑ>
を実装する場合など、一意の型C<U₁...Uₑ>
があります。- ("一意性" 制限は、インターフェイス
C<T>{} class U: C<X>, C<Y>{}
の場合、U
からC<T>
への推論では、U₁
がX
またはY
可能性があるため、推論は行われません。
これらのケースのいずれかが適用される場合、次のように、各Uᵢ
から対応するVᵢ
への推論が行われます。 Uᵢ
が参照型であることが不明な場合は、exact 推論が行われます- それ以外の場合、
U
が配列型の場合は、lower バインド推論が行われます - それ以外の場合、
V
がC<V₁...Vₑ>
場合、推論はC
のi-th
型パラメーターに依存します。- 共変の場合は、 lower バインド推論 が行われます。
- 反変の場合は、 upper バインド推論 が行われます。
- インバリアントの場合は、 exact 推論 が行われます。
- それ以外の場合、推論は行われません。
12.6.3.11 上限推論
型U
から型V
は次のように行われます。
V
が unfixed の 1 つである場合Xᵢ
U
はXᵢ
の上限のセットに追加されます。- それ以外の場合、
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ₑ>
であり、V
はclass, struct, interface
型またはdelegate
型であり、一意の型にidentical
、直接または間接的にinherits
、または (直接的または間接的に) 一意の型を実装しますC<V₁...Vₑ>
- ("一意性" 制限は、インターフェイスが
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
されると、C<U₁>
からV<Q>
への推論時に推論が行われないことを意味します。推論は、U₁
からX<Q>
またはY<Q>
) に対して行われません。
これらのケースのいずれかが適用される場合、次のように、各Uᵢ
から対応するVᵢ
への推論が行われます。 Uᵢ
が参照型であることが不明な場合は、exact 推論が行われます- それ以外の場合、
V
が配列型の場合は、upper バインド推論が行われます - それ以外の場合、
U
がC<U₁...Uₑ>
場合、推論はC
のi-th
型パラメーターに依存します。- 共変の場合は、 upper バインド推論 が行われます。
- 反変の場合は、 より低い推論 が行われます。
- インバリアントの場合は、 exact 推論 が行われます。
- それ以外の場合、推論は行われません。
12.6.3.12 修正
一連の境界を持つ固定型変数Xᵢ
は次のように修正されます。
- candidate 型のセット
Uₑ
は、Xᵢ
の境界セット内のすべての型のセットとして開始されます。 Xᵢ
の各バインドが順番に調べられ、Xᵢ
の正確なバインドされた U ごとに、U
と同じではないすべての型Uₑ
が候補セットから削除されます。 存在しないすべての型Uₑ
Xᵢ
の下限U
ごとにU
からの暗黙的な変換が候補セットから削除されます。 存在しないすべての型Uₑ
Xᵢ
の各上限 U についてU
への暗黙的な変換が候補セットから削除されます。- 他のすべての候補型から暗黙的な変換がある一意の型
V
Uₑ
残りの候補の型の中に存在する場合、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 引数はソース パラメーターに関連し、
TSource
をCustomer
と推論します。 次に、上記の匿名関数型推論プロセスを使用して、c
型Customer
が与えられ、式c.Name
はセレクター パラメーターの戻り値の型に関連し、string
TResult
推論します。 したがって、呼び出しはSequence.Select<Customer,string>(customers, (Customer c) => c.Name)
結果は
IEnumerable<string>
型です。次の例は、匿名関数型の推論で、ジェネリック メソッド呼び出しの引数間で型情報を "フロー" できるようにする方法を示しています。 次のメソッドと呼び出しを指定します。
class A { static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) { return f2(f1(value)); } static void M() { double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours); } }
呼び出しの型推論は次のように続行されます。まず、引数 "1:15:30" は value パラメーターに関連し、
X
を文字列として推論します。 次に、最初の匿名関数のパラメーターs
、推論された型string
が与えられ、式TimeSpan.Parse(s)
はf1
の戻り値の型に関連し、System.TimeSpan
Y
推論されます。 最後に、2 番目の匿名関数 (t
) のパラメーターには推論された型System.TimeSpan
が与えられ、式t.TotalHours
はf2
の戻り値の型に関連し、double
Z
推論されます。 したがって、呼び出しの結果はdouble
型になります。end の例
12.6.3.14 メソッド グループの変換のための型推論
ジェネリック メソッドの呼び出しと同様に、ジェネリック メソッドを含むメソッド グループ M
が特定のデリゲート型 D
に変換されるときにも、型推論が適用されます (§10.8)。 メソッドを指定する
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
型推論のタスクD
デリゲート型に割り当てられているメソッド グループM
は、式が次のようにS₁...Sᵥ
型引数を検索することです。
M<S₁...Sᵥ>
は、D
と互換性 (§20.2) になります。
ジェネリック メソッド呼び出しの型推論アルゴリズムとは異なり、この場合、引数 types、引数 式はありません。 特に、匿名関数がないため、推論の複数のフェーズは必要ありません。
代わりに、すべてのXᵢ
が unfixed と見なされ、lower バインド推論はD
to の各引数型Uₑ
M
の対応するパラメーター型Tₑ
されます。 いずれかの Xᵢ
で境界が見つからなかった場合、型の推論は失敗します。 それ以外の場合、すべてのXᵢ
が型推論の結果である対応するSᵢ
に修正されます。
12.6.3.15 一連の式の最も一般的な型を見つける
場合によっては、一連の式について共通の型を推論する必要があります。 特に、暗黙的に型指定された配列の要素型と、 block 本体を持つ匿名関数の戻り値の型は、この方法で見つかります。
E₁...Eᵥ
一連の式に最も一般的な型は、次のように決定されます。
- 新しい 修正されていない 型変数
X
が導入されました。 - 各式
Ei
出力型推論 (§12.6.3.7) が実行されてX
されます。 X
は 修正 (§12.6.3.12) であり、結果の型は最も一般的な型です。- それ以外の場合、推論は失敗します。
注: 直感的にこの推論は、
Eᵢ
を引数としてvoid M<X>(X x₁ ... X xᵥ)
メソッドを呼び出し、X
を推論することと同じです。 end note
12.6.4 オーバーロードの解決
12.6.4.1 全般
オーバーロード解決は、引数リストと候補関数メンバーのセットを指定して呼び出す最適な関数メンバーを選択するためのバインディング時メカニズムです。 オーバーロード解決は、C# 内の次の個別のコンテキストで呼び出す関数メンバーを選択します。
- invocation_expressionで名前が付けられたメソッドの呼び出し (§12.8.10)。
- object_creation_expressionで名前が付けられたインスタンス コンストラクターの呼び出し (§12.8.17.2)。
- element_accessを介したインデクサー アクセサーの呼び出し (§12.8.12)。
- 式で参照される定義済み演算子またはユーザー定義演算子の呼び出し (§12.4.4 および §12.4.5)。
これらの各コンテキストは、候補関数メンバーのセットと、独自の方法で引数のリストを定義します。 たとえば、メソッド呼び出しの候補のセットには、オーバーライドとしてマークされたメソッド (§12.5) は含まれません。また、派生クラス内のメソッドが該当する場合 (§12.8.10.2) 基底クラスのメソッドは候補になりません。
候補関数メンバーと引数リストが特定されると、最適な関数メンバーの選択はすべてのケースで同じです。
- まず、候補関数メンバーのセットは、指定された引数リスト (§12.6.4.2) に適用できる関数メンバーに縮小されます。 この縮小セットが空の場合、コンパイル時エラーが発生します。
- 次に、適用可能な候補関数メンバーのセットから最適な関数メンバーが配置されます。 セットに含まれる関数メンバーが 1 つだけの場合、その関数メンバーが最適な関数メンバーになります。 それ以外の場合は、各関数メンバーが §12.6.4.3 の規則を使用して他のすべての関数メンバーと比較される場合に限り、指定された引数リストに関して他のすべての関数メンバーよりも優れている 1 つの関数メンバーが最適です。 他のすべての関数メンバーよりも優れた関数メンバーが 1 つだけ存在しない場合、関数メンバーの呼び出しはあいまいになり、バインド時エラーが発生します。
次のサブクラウスは、適用可能な関数メンバー関数メンバーという用語の正確な意味を定義します。
12.6.4.2 該当する関数メンバー
関数メンバーは、次のすべてに該当する場合にA
引数リストに関して適用可能な関数メンバーと呼びます。
A
の各引数は、§12.6.2.2 で説明されている関数メンバー宣言のパラメーターに対応し、最大 1 つの引数が各パラメーターに対応し、引数が対応しないパラメーターは省略可能なパラメーターです。A
の各引数について、引数のパラメーター受け渡しモードは、対応するパラメーターのパラメーター受け渡しモードと同じです。
パラメーター配列を含む関数メンバーの場合、関数メンバーが上記の規則で適用できる場合は、 正規形式で適用できると言われます。 パラメーター配列を含む関数メンバーが通常の形式で適用できない場合、関数メンバーは代わりにその拡張形式で適用できます
- 展開されたフォームは、関数メンバー宣言のパラメーター配列をパラメーター配列の要素型の 0 個以上の値パラメーターに置き換えて、引数リスト
A
の引数の数がパラメーターの合計数と一致します。A
が関数メンバー宣言の固定パラメーターの数よりも少ない引数を持つ場合、関数メンバーの拡張形式を構築できないため、適用できません。 - それ以外の場合、展開されたフォームは、
A
の各引数に適用されます。次のいずれかが当てはまります。
引数の型から入力パラメーターのパラメーター型への暗黙的な変換が動的暗黙的な変換 (§10.2.10) である場合、結果は未定義になります。
例: 次の宣言とメソッド呼び出しが考えられます。
public static void M1(int p1) { ... } public static void M1(in int p1) { ... } public static void M2(in int p1) { ... } public static void Test() { int i = 10; uint ui = 34U; M1(in i); // M1(in int) is applicable M1(in ui); // no exact type match, so M1(in int) is not applicable M1(i); // M1(int) and M1(in int) are applicable M1(i + 5); // M1(int) and M1(in int) are applicable M1(100u); // no implicit conversion exists, so M1(int) is not applicable M2(in i); // M2(in int) is applicable M2(i); // M2(in int) is applicable M2(i + 5); // M2(in int) is applicable }
end の例
- 静的メソッドは、メソッド グループが型を介して simple_name または member_access から発生する場合にのみ適用されます。
- インスタンス メソッドは、メソッド グループが simple_name、変数または値を介した member_access 、または base_accessの結果である場合にのみ適用されます。
- メソッド グループの結果が simple_nameの場合、インスタンス メソッドは、
this
アクセスが許可されている場合にのみ適用 §12.8.14。
- メソッド グループの結果が simple_nameの場合、インスタンス メソッドは、
- §12.8.7.2 で説明されているように、メソッド グループがインスタンスまたは型のいずれかを介して実行できるmember_accessから得られる場合インスタンスメソッドと静的メソッドの両方が適用されます。
- 型引数 (明示的に指定または推論) が制約を満たしていないジェネリック メソッドは適用できません。
- メソッド グループ変換のコンテキストでは、メソッドの戻り値の型からデリゲートの戻り値の型への ID 変換 (§10.2.2) または暗黙的な参照変換 (§10.2.8) が存在する必要があります。 それ以外の場合、候補メソッドは適用されません。
12.6.4.3 Better 関数メンバー
より優れた関数メンバーを決定するために、削除された引数リスト A
は、元の引数リストに表示される順序で引数式自体だけを含み、 out
または ref
引数を除外して構築されます。
各候補関数メンバーのパラメーター リストは、次のように構築されます。
- 展開フォームは、関数メンバーが展開フォームでのみ適用された場合に使用されます。
- 対応する引数のない省略可能なパラメーターは、パラメーター リストから削除されます
- パラメーター リストから参照パラメーターと出力パラメーターが削除される
- パラメーターは、引数リスト内の対応する引数と同じ位置に配置されるように並べ替えられます。
引数リストA
一連の引数式が{E₁, E₂, ..., Eᵥ}
され、2 つの適用可能な関数メンバーがMᵥ
され、パラメーター型が{P₁, P₂, ..., Pᵥ}
および{Q₁, Q₂, ..., Qᵥ}
でMₓ
されると、Mᵥ
はMₓ
よりもbetter 関数メンバーとして定義されます。
- 引数ごとに、
Eᵥ
からQᵥ
への暗黙的な変換は、Eᵥ
からPᵥ
への暗黙的な変換よりも優れたものではありません。 - 少なくとも 1 つの引数の場合、
Eᵥ
からPᵥ
への変換は、Eᵥ
からQᵥ
への変換よりも優れています。
パラメーターの型シーケンス {P₁, P₂, ..., Pᵥ}
と {Q₁, Q₂, ..., Qᵥ}
が等しい場合 (つまり、各 Pᵢ
は対応する Qᵢ
への ID 変換を持ちます)、より適切な関数メンバーを決定するために、次のタイブレーク ルールが適用されます。
Mᵢ
が非ジェネリック メソッドであり、Mₑ
がジェネリック メソッドである場合、Mᵢ
はMₑ
よりも優れています。- それ以外の場合、
Mᵢ
が通常の形式で適用され、Mₑ
に params 配列があり、展開された形式でのみ適用できる場合は、Mₑ
よりもMᵢ
が適しています。 - それ以外の場合、両方のメソッドがパラメーター配列を持ち、展開されたフォームでのみ適用可能であり、
Mᵢ
の params 配列の要素がMₑ
の params 配列よりも少ない場合、Mᵢ
はMₑ
よりも優れています。 - それ以外の場合、
Mᵥ
にMₓ
よりも具体的なパラメーター型がある場合、Mᵥ
はMₓ
よりも優れています。{R1, R2, ..., Rn}
と{S1, S2, ..., Sn}
は、Mᵥ
とMₓ
の予期しないパラメーター型と、予期しないパラメーター型を表します。Mᵥ
のパラメーター型は、各パラメーターに対して、Rx
がSx
よりも具体的でない場合に、Mₓ
よりも具体的であり、少なくとも 1 つのパラメーターに対して、Rx
がSx
よりも具体的である場合です。- 型パラメーターは、型以外のパラメーターよりも具体的ではありません。
- 少なくとも 1 つの型引数がより具体的で、型引数が他の型引数の対応する型引数よりも具体的でない場合、構築された型は、(同じ数の型引数を持つ) 別の構築型よりも具体的です。
- 配列型は、1 つ目の要素型が 2 番目の要素型よりも具体的な場合、(次元数が同じ) 別の配列型よりも具体的です。
- それ以外の場合は、1 つのメンバーが非リフト演算子で、もう一方がリフトされた演算子である場合は、非リフト演算子の方が適しています。
- どちらの関数メンバーも適切でなく、
Mᵥ
のすべてのパラメーターに対応する引数があるのに対し、既定の引数はMₓ
の少なくとも 1 つの省略可能なパラメーターに置き換える必要がある場合、Mᵥ
はMₓ
よりも優れています。 - 少なくとも 1 つのパラメーター
Mᵥ
で、Mₓ
の対応するパラメーターよりも better パラメーター渡しの選択肢 (§12.6.4.4) を使用し、Mₓ
のどのパラメーターもMᵥ
よりも適切なパラメーター渡しの選択肢を使用しない場合、Mᵥ
はMₓ
よりも優れています。 - それ以外の場合は、関数メンバーの方が適していません。
12.6.4.4 パラメーター受け渡しモードの改善
次のように、2 つのパラメーターの 1 つに値渡しモードがある場合、2 つのオーバーロードされたメソッドで対応するパラメーターがパラメーター渡しモードによってのみ異なることがあります。
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
int i = 10;
、§12.6.4.2 によると、呼び出しM1(i)
とM1(i + 5)
の両方のオーバーロードが適用されます。 このような場合、パラメーター受け渡しモードの値を持つメソッドは、 better パラメーターパッシング モードの選択肢です。
注: 入力、出力、または参照の受け渡しモードの引数は、まったく同じパラメーターの受け渡しモードにのみ一致するので、このような選択は必要ありません。 end note
12.6.4.5 式からの変換の改善
式E
から型T₁
に変換する暗黙的な変換C₁
と、式E
から型T₂
に変換する暗黙的な変換C₂
を考えると、C₁
はより次のいずれかが保持されている場合C₂
よりもです。
E
T₁
と完全に一致し、E
がT₂
と完全に一致しません (§12.6.4.6)E
T₁
とT₂
の両方または両方に完全に一致し、T₁
はT₂
よりも優れた変換ターゲットです (§12.6.4.7)E
はメソッド グループ (§12.2) であり、T₁
は変換C₁
のメソッド グループの単一の最適なメソッドと互換性があり (§20.4)、T₂
は変換用のメソッド グループの単一の最適なメソッドと互換性がありませんC₂
12.6.4.6 厳密に一致する式
式E
と型T
E
、次のいずれかが保持されている場合は T
完全に一致します。
E
には型S
があり、id 変換はS
からT
E
は匿名関数です。T
はデリゲート型D
または式ツリー型Expression<D>
であり、次のいずれかが保持されます。D
のパラメーター リスト (§12.6.3.12) のコンテキストにE
の推定戻り値の型X
が存在し、X
から戻り値の型への ID 変換が存在しますD
E
は戻り値のないasync
ラムダであり、D
には非ジェネリックである戻り値の型があります«TaskType»
E
が非非同期で、D
が戻り値の型Y
を持つか、E
が非同期でD
戻り値の型が«TaskType»<Y>
(§15.15.1) であり、次のいずれかが保持されます。E
の本体は、完全に一致する式です。Y
E
の本体は、すべての return ステートメントが完全に一致する式を返すブロックですY
12.6.4.7 コンバージョンターゲットの向上
T₁
とT₂
の 2 つの型を指定すると、T₁
は、次のいずれかが保持されている場合T₂
よりもな変換ターゲットになります。
T₁
からT₂
への暗黙的な変換が存在し、T₂
からT₁
への暗黙的な変換は存在しませんT₁
は«TaskType»<S₁>
(§15.15.1)、T₂
は«TaskType»<S₂>
、S₁
は変換ターゲットよりも優れていますS₂
T₁
は«TaskType»<S₁>
(§15.15.1)、T₂
は«TaskType»<S₂>
であり、T₁
はT₂
T₁
はS₁
またはS₁?
S₁
が符号付き整数型で、T₂
がS₂
またはS₂?
S₂
が符号なし整数型である場合です。 具体的には:S₁
がsbyte
であり、S₂
がbyte
、ushort
、uint
、またはulong
S₁
がshort
であり、S₂
がushort
、uint
、またはulong
S₁
がint
であり、S₂
がuint
されているか、ulong
S₁
はlong
され、S₂
はulong
12.6.4.8 ジェネリック クラスでのオーバーロード
注: 宣言されているシグネチャは一意である必要があります (§8.6)。型引数を置換すると、同じシグネチャになる可能性があります。 このような状況では、オーバーロードの解決では、元のシグネチャ (型引数の置換前) の最も具体的な (§12.6.4.3) が選択され、存在する場合はエラーが報告されます。 end note
例: 次の例は、この規則に従って有効で無効なオーバーロードを示しています。
public interface I1<T> { ... } public interface I2<T> { ... } public abstract class G1<U> { public abstract int F1(U u); // Overload resolution for G<int>.F1 public abstract int F1(int i); // will pick non-generic public abstract void F2(I1<U> a); // Valid overload public abstract void F2(I2<U> a); } abstract class G2<U,V> { public abstract void F3(U u, V v); // Valid, but overload resolution for public abstract void F3(V v, U u); // G2<int,int>.F3 will fail public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail public abstract void F5(U u1, I1<V> v2); // Valid overload public abstract void F5(V v1, U u2); public abstract void F6(ref U u); // Valid overload public abstract void F6(out V v); }
end の例
12.6.5 動的メンバー呼び出しのコンパイル時チェック
動的にバインドされた操作のオーバーロード解決は実行時に行われますが、コンパイル時にオーバーロードの選択元となる関数メンバーの一覧を把握できる場合があります。
- デリゲート呼び出し (§12.8.10.4) の場合、リストは呼び出しの delegate_type と同じパラメーター リストを持つ 1 つの関数メンバーです
- メソッドの呼び出し (§12.8.10.2) の場合、または静的な型が動的でない値の場合、メソッド グループ内のアクセス可能なメソッドのセットはコンパイル時に認識されます。
- オブジェクト作成式 (§12.8.17.2) の場合、型内のアクセス可能なコンストラクターのセットはコンパイル時に認識されます。
- インデクサー アクセス (§12.8.12.3) の場合、受信側のアクセス可能なインデクサーのセットはコンパイル時に認識されます。
このような場合は、既知の関数メンバーセット内の各メンバーに対して制限付きコンパイル時チェックが実行され、実行時に呼び出されることがないことがわかっているかどうかを確認します。 変更されたパラメーター F
関数メンバーごとに、引数リストが作成されます。
- 最初に、
F
がジェネリック メソッドであり、型引数が指定された場合は、パラメーター リスト内の型パラメーターに置き換えます。 ただし、型引数が指定されていない場合、このような置換は行われません。 - 次に、型が開いているすべてのパラメーター (つまり、型パラメーターが含まれています。 §8.4.3 を参照) は、対応するパラメーターと共に省略されます。
F
が小切手に合格するためには、以下のすべてが保持されます。
F
の変更されたパラメーター リストは、§12.6.4.2 の観点から変更された引数リストに適用。- 変更されたパラメーター リスト内のすべての構築された型は、制約を満たす (§8.4.5)。
- 上記の手順で
F
の型パラメーターが置き換えられた場合、それらの制約は満たされます。 F
が静的メソッドの場合、メソッド グループは、コンパイル時にレシーバーが変数または値であることがわかっているmember_accessから発生したものではありません。F
がインスタンス メソッドの場合、メソッド グループは、コンパイル時にレシーバーが型であることがわかっているmember_accessから発生したものではありません。
このテストに合格した候補がない場合は、コンパイル時エラーが発生します。
12.6.6 関数メンバーの呼び出し
12.6.6.1 全般
このサブクラウズでは、特定の関数メンバーを呼び出すために実行時に実行されるプロセスについて説明します。 バインディング時プロセスは、呼び出す特定のメンバーを既に決定していることを前提としています。場合によっては、一連の候補関数メンバーにオーバーロード解決を適用します。
呼び出しプロセスを記述するために、関数メンバーは次の 2 つのカテゴリに分類されます。
- 静的関数メンバー。 これらは、静的メソッド、静的プロパティ アクセサー、およびユーザー定義演算子です。 静的関数メンバーは常に非仮想です。
- インスタンス関数メンバー。 これらは、インスタンス メソッド、インスタンス コンストラクター、インスタンス プロパティ アクセサー、およびインデクサー アクセサーです。 インスタンス関数メンバーは非仮想または仮想であり、常に特定のインスタンスで呼び出されます。 インスタンスはインスタンス式によって計算され、
this
として関数メンバー内でアクセスできるようになります (§12.8.14)。 インスタンス コンストラクターの場合、インスタンス式は新しく割り当てられたオブジェクトになります。
関数メンバー呼び出しの実行時処理は、次の手順で構成されます。ここで、 M
は関数メンバーであり、 M
がインスタンス メンバーの場合はインスタンス式 E
。
M
が静的関数メンバーの場合:- 引数リストは、 §12.6.2 で説明されているように評価されます。
M
が呼び出されます。
それ以外の場合、
E
の型が値型のV
であり、M
がV
で宣言またはオーバーライドされる場合:E
が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。 インスタンス コンストラクターの場合、この評価は、新しいオブジェクトにストレージ (通常は実行スタックから) を割り当てることで構成されます。 この場合、E
は変数として分類されます。E
が変数として分類されていない場合、またはV
が読み取り専用構造体型 (§16.2.2) でない場合、E
は次のいずれかです。- 入力パラメーター (§15.6.2.3.2) または
readonly
フィールド (§15.5.3) またはreadonly
参照変数または戻り値 (§9.7)
その後、
E
の型の一時ローカル変数が作成され、E
の値がその変数に割り当てられます。E
は、その一時ローカル変数への参照として再分類されます。 一時変数は、M
内でthis
としてアクセスできますが、他の方法ではアクセスできません。 したがって、E
を書き込むことができる場合にのみ、呼び出し元がthis
に加える変更M
観察することができます。- 引数リストは、 §12.6.2 で説明されているように評価されます。
M
が呼び出されます。E
によって参照される変数は、this
によって参照される変数になります。
それ以外:
E
が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。- 引数リストは、 §12.6.2 で説明されているように評価されます。
E
の型がvalue_typeの場合、E
をclass_typeに変換するためにボックス化変換 (§10.2.9) が実行され、E
は次の手順でそのclass_typeと見なされます。 value_typeがenum_typeの場合、class_typeはSystem.Enum;
され、それ以外の場合はSystem.ValueType
。E
の値が有効であることが確認されます。E
の値が null の場合、System.NullReferenceException
がスローされ、それ以上の手順は実行されません。- 呼び出す関数メンバーの実装が決定されます。
E
のバインド時の型がインターフェイスの場合、呼び出す関数メンバーは、E
によって参照されるインスタンスのランタイム型によって提供されるM
の実装です。 この関数メンバーは、インターフェイス マッピング 規則 (§18.6.5) を適用して、E
によって参照されるインスタンスのランタイム型によって提供されるM
の実装を決定することによって決定されます。- それ以外の場合、
M
が仮想関数メンバーの場合、呼び出す関数メンバーは、E
によって参照されるインスタンスのランタイム型によって提供されるM
の実装です。 この関数メンバーは、E
によって参照されるインスタンスのランタイム型に関して、M
の最も派生した実装 (§15.6.4) を決定するための規則を適用することによって決定されます。 - それ以外の場合、
M
は非仮想関数メンバーであり、呼び出す関数メンバーはそれ自体M
。
- 上記の手順で決定した関数メンバーの実装が呼び出されます。
E
によって参照されるオブジェクトは、このオブジェクトによって参照されるオブジェクトになります。
インスタンス コンストラクター (§12.8.17.2) の呼び出しの結果は、作成された値です。 他の関数メンバーの呼び出しの結果は、その本体から返された値 (存在する場合) です (§13.10.5)。
12.6.6.2 ボックス化されたインスタンスでの呼び出し
value_typeに実装されている関数メンバーは、次の状況で、そのvalue_typeのボックス化されたインスタンスを介して呼び出すことができます。
- 関数メンバーが型 class_type から継承されたメソッドのオーバーライドであり、その class_typeのインスタンス式を介して呼び出される場合。
注: class_type は常に、
System.Object
、System.ValueType
、またはSystem.Enum
のいずれかになります。 end note - 関数メンバーがインターフェイス関数メンバーの実装であり、 interface_typeのインスタンス式を介して呼び出される場合。
- デリゲートを介して関数メンバーが呼び出されたとき。
このような状況では、ボックス化されたインスタンスには value_typeの変数が含まれていると見なされ、この変数は関数メンバー呼び出し内でこの変数によって参照される変数になります。
注: 特に、これは、ボックス化されたインスタンスで関数メンバーが呼び出されたときに、関数メンバーがボックス化されたインスタンスに含まれる値を変更できる可能性があることを意味します。 end note
12.7 分解
分解とは、式が個々の式のタプルに変換されるプロセスです。 分解は、単純な代入のターゲットがタプル式である場合に、そのタプルの各要素に割り当てる値を取得するために使用されます。
式E
は、次のようにn
要素を持つタプル式に分解されます。
E
がn
要素を持つタプル式の場合、分解の結果は式自体E
。- それ以外の場合、
E
n
要素を持つタプル型(T1, ..., Tn)
がある場合、E
は一時変数__v
に評価され、分解の結果は式(__v.Item1, ..., __v.Itemn)
になります。 - それ以外の場合、式
E.Deconstruct(out var __v1, ..., out var __vn)
コンパイル時に一意のインスタンスまたは拡張メソッドに解決された場合、その式が評価され、分解の結果は式(__v1, ..., __vn)
。 このようなメソッドは、 deconstructor と呼ばれます。 - それ以外の場合、
E
を分解することはできません。
ここでは、 __v
と __v1, ..., __vn
、それ以外の場合は非表示でアクセスできない一時変数を参照します。
注:
dynamic
型の式を分解することはできません。 end note
12.8 主な式
12.8.1 全般
主な式には、最も単純な形式の式が含まれます。
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
;
primary_no_array_creation_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| tuple_expression
| member_access
| null_conditional_member_access
| invocation_expression
| element_access
| null_conditional_element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| sizeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
注: これらの文法規則は、ANTLR が処理しない一連の相互左再帰規則 (
primary_expression
、primary_no_array_creation_expression
、member_access
、invocation_expression
、element_access
、post_increment_expression
、post_decrement_expression
、null_forgiving_expression
、pointer_member_access
、pointer_element_access
) の一部であるため、ANTLR 対応ではありません。 標準の手法を使用して文法を変換し、相互左再帰を削除できます。 これは、すべての解析戦略がそれを必要とするわけではないため(LALRパーサーではそうはいかない)、これを行うと構造と説明が難読化されるわけではありません。 end note
pointer_member_access (§23.6.3) と pointer_element_access (§23.6.4) は、安全でないコード (§23) でのみ使用できます。
プライマリ式は、 array_creation_expressionと primary_no_array_creation_expressionの間で分割されます。 array_creation_expressionを他の単純な式形式と共に一覧表示するのではなく、このように扱うことで、文法で混乱を招く可能性のあるコード (例:
object o = new int[3][1];
それ以外の場合は次のように解釈されます。
object o = (new int[3])[1];
12.8.2 リテラル
literal (§6.4.5) で構成されるprimary_expressionは、値として分類されます。
12.8.3 挿入文字列式
interpolated_string_expressionは、$
、$@
、または@$
で構成され、その直後に"
文字内のテキストが続きます。 引用符で囲まれたテキスト内には、0 個以上のinterpolations{
および}
文字で区切られ、それぞれが式および省略可能な書式指定を囲みます。
挿入文字列式には 2 つの形式があります。regular (interpolated_regular_string_expression) と verbatim (interpolated_verbatim_string_expression)。構文的には似ていますが、意味的には異なる 2 つの形式の文字列リテラル (§6.4.5.6)。
interpolated_string_expression
: interpolated_regular_string_expression
| interpolated_verbatim_string_expression
;
// interpolated regular string expressions
interpolated_regular_string_expression
: Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
Interpolated_Regular_String_End
;
regular_interpolation
: expression (',' interpolation_minimum_width)?
Regular_Interpolation_Format?
;
interpolation_minimum_width
: constant_expression
;
Interpolated_Regular_String_Start
: '$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Regular_String_Mid
: Interpolated_Regular_String_Element+
;
Regular_Interpolation_Format
: ':' Interpolated_Regular_String_Element+
;
Interpolated_Regular_String_End
: '"'
;
fragment Interpolated_Regular_String_Element
: Interpolated_Regular_String_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Regular_String_Character
// Any character except " (U+0022), \\ (U+005C),
// { (U+007B), } (U+007D), and New_Line_Character.
: ~["\\{}\u000D\u000A\u0085\u2028\u2029]
;
// interpolated verbatim string expressions
interpolated_verbatim_string_expression
: Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
Interpolated_Verbatim_String_End
;
verbatim_interpolation
: expression (',' interpolation_minimum_width)?
Verbatim_Interpolation_Format?
;
Interpolated_Verbatim_String_Start
: '$@"'
| '@$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Verbatim_String_Mid
: Interpolated_Verbatim_String_Element+
;
Verbatim_Interpolation_Format
: ':' Interpolated_Verbatim_String_Element+
;
Interpolated_Verbatim_String_End
: '"'
;
fragment Interpolated_Verbatim_String_Element
: Interpolated_Verbatim_String_Character
| Quote_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Verbatim_String_Character
: ~["{}] // Any character except " (U+0022), { (U+007B) and } (U+007D)
;
// lexical fragments used by both regular and verbatim interpolated strings
fragment Open_Brace_Escape_Sequence
: '{{'
;
fragment Close_Brace_Escape_Sequence
: '}}'
;
上記で定義した 6 つの構文規則は、次のようにコンテキストに依存します。
Rule | コンテキスト要件 |
---|---|
Interpolated_Regular_String_Mid | Interpolated_Regular_String_Start後、次の補間の間、および対応するInterpolated_Regular_String_Endの前にのみ認識されます。 |
Regular_Interpolation_Format | regular_interpolation内でのみ認識され、開始コロン (:)が任意の種類の角かっこ (かっこ/中かっこ/正方形) 内に入れ子になっていない場合。 |
Interpolated_Regular_String_End | Interpolated_Regular_String_Startの後でのみ認識され、介入するトークンがInterpolated_Regular_String_Midまたはregular_interpolationの一部となり得るトークン (そのような補間に含まれるinterpolated_regular_string_expressionのトークンを含む) である場合にのみ認識されます。 |
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End | これら 3 つの規則の認識は、上記の対応する規則に従い、 規則 対応する verbatim 1 に置き換えられました。 |
注: 上記の規則は、定義が言語内の他のトークンの定義と重複する場合、コンテキストに依存します。 end note
注: 上記の文法は、コンテキストに依存する字句規則のため、ANTLR 対応ではありません。 他の lexer ジェネレーターと同様に、ANTLR はコンテキストに依存する字句規則をサポートします。たとえば、 非現実的なモードを使用しますがこれは実装の詳細であるため、この仕様の一部ではありません。 end note
interpolated_string_expressionは値として分類されます。 暗黙的な挿入文字列変換 (§10.2.5) を使用してSystem.IFormattable
またはSystem.FormattableString
にすぐに変換される場合、挿入文字列式にはその型があります。 それ以外の場合は、型が string
。
注: interpolated_string_expression で使用できる型の違いは、
System.String
(§C.2) とSystem.FormattableString
(§C.3) のドキュメントから決定できます。 end note
補間の意味は、regular_interpolationとverbatim_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_FormatとVerbatim_Interpolation_Formatで同じ標準形式の説明は、System.IFormattable
(§C.4) のドキュメントと、標準ライブラリ (§C) の他の型で確認できます。 end note
interpolation_minimum_widthでは、constant_expressionはint
への暗黙的な変換を持つ必要があります。 フィールドの幅このconstant_expressionの絶対値にし、アライメントこのconstant_expressionの値の符号 (正または負) にします。
- フィールド幅の値が書式設定された文字列の長さ以下の場合、書式設定された文字列は変更されません。
- それ以外の場合、書式設定された文字列の長さがフィールドの幅と等しくなるように、空白文字が埋め込まれます。
- 配置が正の場合、書式設定された文字列はパディングの前に置くことで右揃えになります。
- それ以外の場合は、パディングを追加して左揃えになります。
上記の書式設定や補間の埋め込みを含む、interpolated_string_expressionの全体的な意味 は、式からメソッド呼び出しへの変換によって定義されます。式の型がSystem.IFormattable
またはSystem.FormattableString
場合、そのメソッドはSystem.FormattableString
型の値を返すSystem.Runtime.CompilerServices.FormattableStringFactory.Create
(§C.3) です。それ以外の場合は、型がstring
され、メソッドはstring
型の値を返すstring.Format
(§C.2) になります。
どちらの場合も、呼び出しの引数リストは、形式文字列リテラル書式指定補間ごとに、および書式指定に対応する各式の引数で構成されます。
書式指定文字列リテラルは次のように構築されます。ここで、 N
は interpolated_string_expression内の補間の数です。 書式指定文字列リテラルは、次の順序で構成されます。
- Interpolated_Regular_String_StartまたはInterpolated_Verbatim_String_Startの文字
- Interpolated_Regular_String_MidまたはInterpolated_Verbatim_String_Midの文字 (存在する場合)
- 次に、
0
からN-1
にI
各数値にN ≥ 1
する場合:- プレースホルダーの仕様:
- 左中かっこ (
{
) 文字 - の 10 進表現
I
- 次に、対応する regular_interpolation または verbatim_interpolation に interpolation_minimum_widthがある場合は、コンマ (
,
) の後に constant_expressionの値の 10 進表現が続きます。 - 対応するregular_interpolationまたはverbatim_interpolationのRegular_Interpolation_FormatまたはVerbatim_Interpolation_Formatの文字 (存在する場合)
- 右中かっこ (
}
) 文字
- 左中かっこ (
- 対応する補間の直後に Interpolated_Regular_String_Mid または Interpolated_Verbatim_String_Mid の文字 (存在する場合)
- プレースホルダーの仕様:
- 最後に、 Interpolated_Regular_String_End または Interpolated_Verbatim_String_Endの文字。
後続の引数は、補間からの 式です (存在する場合)。
interpolated_string_expressionに複数の補間が含まれている場合、それらの補間の式は左から右のテキスト順で評価されます。
例:
この例では、次の書式指定機能を使用します。
- 整数を大文字の 16 進数として書式設定する
X
書式指定。 string
値の既定の形式は値自体です。- 指定された最小フィールド幅内で右揃えする正の配置値
- 指定された最小フィールド幅内で左揃えの負の配置値、
- interpolation_minimum_widthの定義済み定数、および
{{
と}}
は、それぞれ{
と}
として書式設定されます。
たとえば、以下のように指定したとします。
string text = "red";
int number = 14;
const int width = -4;
その後、以下を実行します。
挿入文字列式 | 同等の意味 string |
Value |
---|---|---|
$"{text}" |
string.Format("{0}", text) |
"red" |
$"{{text}}" |
string.Format("{{text}}) |
"{text}" |
$"{ text , 4 }" |
string.Format("{0,4}", text) |
" red" |
$"{ text , width }" |
string.Format("{0,-4}", text) |
"red " |
$"{number:X}" |
string.Format("{0:X}", number) |
"E" |
$"{text + '?'} {number % 3}" |
string.Format("{0} {1}", text + '?', number % 3) |
"red? 2" |
$"{text + $"[{number}]"}" |
string.Format("{0}", text + string.Format("[{0}]", number)) |
"red[14]" |
$"{(number==0?"Zero":"Non-zero")}" |
string.Format("{0}", (number==0?"Zero":"Non-zero")) |
"Non-zero" |
end の例
12.8.4 単純な名前
simple_nameは識別子で構成され、必要に応じて型引数リストが続きます。
simple_name
: identifier type_argument_list?
;
simple_nameはフォーム I
またはフォーム I<A₁, ..., Aₑ>
のいずれかです。ここで、I
は 1 つの識別子で、I<A₁, ..., Aₑ>
は省略可能なtype_argument_listです。 type_argument_listが指定されていない場合は、e
を 0 にすることを検討してください。 simple_nameは次のように評価され、分類されます。
e
が 0 で、simple_name ローカル変数宣言空間 (§7.3) 内にローカル変数、パラメーター、または名前I
の定数が直接含まれている場合、simple_nameはそのローカル変数、パラメーター、または定数を参照し、変数または値として分類されます。e
が 0 で、simple_nameがジェネリック メソッド宣言内でattributesmethod_declarationの外部に表示され、その宣言に名前I
を持つ型パラメーターが含まれている場合、simple_nameはその型パラメーターを参照します。- それ以外の場合は、各インスタンス型
T
(§15.3.2) に対して、すぐに外側の型宣言のインスタンス型から始まり、外側にある各クラスまたは構造体宣言のインスタンス型 (存在する場合) を続行します。e
が 0 で、T
の宣言に名前I
を持つ型パラメーターが含まれている場合、simple_nameはその型パラメーターを参照します。- それ以外の場合、
e
型引数を持つT
内のI
のメンバー参照 (§12.5) が一致を生成する場合:T
が、すぐに囲むクラスまたは構造体の型のインスタンス型であり、ルックアップによって 1 つ以上のメソッドが識別される場合、結果は、this
のインスタンス式が関連付けられたメソッド グループになります。 型引数リストが指定されている場合は、ジェネリック メソッド (§12.8.10.2) の呼び出しで使用されます。- それ以外の場合、
T
がすぐに外側のクラスまたは構造体型のインスタンス型である場合は、 参照によってインスタンス メンバーが識別され、インスタンス コンストラクター、インスタンス メソッド、またはインスタンス アクセサー (§12.2.1 のblock内で参照が発生した場合、結果はフォームthis.I
のメンバー アクセス (§12.8.7) と同じです。 これは、e
が 0 の場合にのみ発生します。 - それ以外の場合、結果はフォーム
T.I
またはT.I<A₁, ..., Aₑ>
のメンバー アクセス (§12.8.7) と同じです。
- それ以外の場合は、simple_nameが発生する名前空間から始まり、外側の各名前空間 (存在する場合) を続行し、グローバル名前空間で終わる名前空間
N
ごとに、エンティティが配置されるまで、次の手順が評価されます。e
が 0 で、I
がN
内の名前空間の名前である場合は、次のようになります。- simple_nameが発生する場所が
N
の名前空間宣言で囲まれており、名前空間宣言に名前I
を名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、simple_nameはあいまいになり、コンパイル時エラーが発生します。 - それ以外の場合、simple_nameは
N
のI
という名前の名前空間を参照します。
- simple_nameが発生する場所が
- それ以外の場合、
N
に名前I
とe
型パラメーターを持つアクセス可能な型が含まれている場合は、次のようになります。e
が 0 で、simple_nameが発生する場所がN
の名前空間宣言で囲まれており、名前空間宣言に名前I
を名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが含まれている場合、simple_nameはあいまいになり、コンパイル時エラーが発生します。- それ以外の場合、 namespace_or_type_name は、指定された型引数で構築された型を参照します。
- それ以外の場合、 simple_name が発生する場所が、
N
の名前空間宣言で囲まれている場合:e
が 0 で、名前I
をインポートされた名前空間または型に関連付けるextern_alias_directiveまたはusing_alias_directiveが名前空間宣言に含まれている場合、simple_nameはその名前空間または型を参照します。- それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前
I
とe
型パラメーターを持つ型が 1 つだけ含まれている場合、 simple_name は、指定された型引数で構築されたその型を参照します。 - それ以外の場合、名前空間宣言の using_namespace_directiveによってインポートされた名前空間に、名前
I
およびe
型パラメーターを持つ複数の型が含まれている場合、 simple_name はあいまいになり、コンパイル時エラーが発生します。
注: このステップ全体は、 namespace_or_type_name (§7.8) の処理における対応するステップとまったく同じになります。 end note
- それ以外の場合、
e
が 0 で、I
が識別子_
の場合、 simple_name は simple discard です。これは宣言式の形式です (§12.17)。 - それ以外の場合、 simple_name は未定義であり、コンパイル時エラーが発生します。
12.8.5 かっこで抽出された式
parenthesized_expressionは、かっこで囲まれた式で構成されます。
parenthesized_expression
: '(' expression ')'
;
parenthesized_expressionは、かっこ内の式を評価することによって評価されます。 かっこ内の 式 が名前空間または型を示している場合は、コンパイル時エラーが発生します。 それ以外の場合、 parenthesized_expression の結果は、含まれる 式の評価の結果。
12.8.6 タプル式
tuple_expressionはタプルを表し、2 つ以上のコンマ区切りで構成され、必要に応じて名前が付けられた expressionかっこで囲まれています。 deconstruction_expressionは、暗黙的に型指定された宣言式を含むタプルの短縮構文です。
tuple_expression
: '(' tuple_element (',' tuple_element)+ ')'
| deconstruction_expression
;
tuple_element
: (identifier ':')? expression
;
deconstruction_expression
: 'var' deconstruction_tuple
;
deconstruction_tuple
: '(' deconstruction_element (',' deconstruction_element)+ ')'
;
deconstruction_element
: deconstruction_tuple
| identifier
;
tuple_expressionはタプルとして分類されます。
deconstruction_expression var (e1, ..., en)
はtuple_expression (var e1, ..., var en)
の短縮形であり、同じ動作に従います。 これは、deconstruction_expression内の入れ子になったdeconstruction_tupleに再帰的に適用されます。 したがって、 deconstruction_expression 内に入れ子になった各識別子は、宣言式 (§12.17) を導入します。 その結果、 deconstruction_expression は単純な割り当ての左側でのみ発生します。
タプル式は、各要素式が型Ti
を持つ場合にのみ、Ei
型を持っています。 型はタプル式と同じアリティのタプル型で、各要素は次のように指定されます。
- 対応する位置のタプル要素に名前
Ni
がある場合、タプル型要素はTi Ni
。 - それ以外の場合、
Ei
がNi
、E.Ni
、またはE?.Ni
の形式の場合、タプル型要素はTi Ni
、次のいずれかを保持します。- タプル式の別の要素に
Ni
という名前があります。 - 名前のない別のタプル要素には、フォーム
Ni
またはE.Ni
またはE?.Ni
のタプル要素式があります。 Ni
はItemX
形式です。ここで、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 つ (
t1
とt2
) はタプル式の型を使用せず、代わりに暗黙的なタプル変換を適用します。t2
の場合、暗黙的なタプル変換は、2
からlong
への暗黙的な変換、およびnull
からstring
への暗黙的な変換に依存します。 3 番目のタプル式には(int i, string)
型があるため、その型の値として再分類できます。 一方、t4
の宣言はエラーです。タプル式には型がないため、2 番目の要素には型がありません。if ((x, y).Equals((1, 2))) { ... };
この例は、タプル式がメソッド呼び出しの唯一の引数である場合に、タプルが複数のかっこのレイヤーにつながる場合があることを示しています。
end の例
12.8.7 メンバー アクセス
12.8.7.1 全般
member_accessは、primary_expression、predefined_type、またはqualified_alias_memberで構成され、その後に ".
" トークンが続き、その後に identifier、必要に応じてtype_argument_listが続きます。
member_access
: primary_expression '.' identifier type_argument_list?
| predefined_type '.' identifier type_argument_list?
| qualified_alias_member '.' identifier type_argument_list?
;
predefined_type
: 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
| 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
| 'ushort'
;
qualified_alias_member運用は、§14.8 で定義されています。
member_accessはフォーム E.I
またはフォーム E.I<A₁, ..., Aₑ>
のいずれかです。ここで、E
はprimary_expression、predefined_type、またはqualified_alias_member、 I
は 1 つの識別子であり、<A₁, ..., Aₑ>
は省略可能なtype_argument_listです。 type_argument_listが指定されていない場合は、e
を 0 にすることを検討してください。
dynamic
型のprimary_expressionを持つmember_accessは動的にバインドされます (§12.3.3)。 この場合、コンパイラはメンバー アクセスを型 dynamic
のプロパティ アクセスとして分類します。 primary_expressionのコンパイル時の型ではなく実行時の型を使用して、実行時にmember_accessの意味を判断するための以下の規則が適用されます。 この実行時分類がメソッド グループにつながる場合、メンバー アクセスはinvocation_expressionのprimary_expressionになります。
member_accessは次のように評価され、分類されます。
e
がゼロで、E
が名前空間であり、E
に名前がI
入れ子になった名前空間が含まれている場合、結果はその名前空間になります。- それ以外の場合、
E
が名前空間であり、E
に名前I
およびK
型パラメーターを持つアクセス可能な型が含まれている場合、その型は指定された型引数で構築されます。 E
が型として分類されている場合、E
が型パラメーターでない場合、およびK
型パラメーターを持つE
のI
のメンバー参照 (§12.5) が一致を生成した場合、E.I
は評価され、次のように分類されます。注: このようなメンバー参照の結果がメソッド グループで、
K
が 0 の場合、メソッド グループには型パラメーターを持つメソッドを含めることができます。 これにより、このようなメソッドを型引数の推論と見なすことができます。 end noteI
が型を識別する場合、結果は、指定された型引数を使用して構築された型になります。I
が 1 つ以上のメソッドを識別する場合、結果はインスタンス式が関連付けられていないメソッド グループになります。I
が静的プロパティを識別する場合、結果は、関連付けられたインスタンス式のないプロパティ アクセスになります。I
が静的フィールドを識別する場合:- フィールドが読み取り専用で、そのフィールドが宣言されているクラスまたは構造体の静的コンストラクターの外部で参照が発生した場合、結果は値、つまり
E
にI
静的フィールドの値になります。 - それ以外の場合、結果は変数、つまり
E
にI
静的フィールドです。
- フィールドが読み取り専用で、そのフィールドが宣言されているクラスまたは構造体の静的コンストラクターの外部で参照が発生した場合、結果は値、つまり
I
が静的イベントを識別する場合:- イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言された場合 (§15.8.1)、
E.I
は静的フィールドであるかのように正確に処理I
。 - それ以外の場合、結果は、インスタンス式が関連付けられていないイベント アクセスになります。
- イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言された場合 (§15.8.1)、
I
が定数を識別する場合、結果は値、つまりその定数の値になります。I
が列挙メンバーを識別する場合、結果は値 (つまり、その列挙メンバーの値) になります。- それ以外の場合、
E.I
は無効なメンバー参照であり、コンパイル時エラーが発生します。
E
がプロパティ アクセス、インデクサー アクセス、変数、または値である場合、その型がT
され、K
型引数を持つT
のI
のメンバー参照 (§12.5) によって一致が生成され、E.I
が評価され、次のように分類されます。- 最初に、
E
がプロパティまたはインデクサー アクセスの場合、プロパティまたはインデクサー アクセスの値が取得され (§12.2.2)、E が値として再分類されます。 I
が 1 つ以上のメソッドを識別する場合、結果は、E
のインスタンス式が関連付けられたメソッド グループになります。I
がインスタンス プロパティを識別する場合、結果は、E
の関連付けられたインスタンス式と、プロパティの型である関連付けられた型を持つプロパティ アクセスになります。T
がクラス型の場合、関連付けられている型は、T
で始まり、その基底クラスを検索するときに見つかったプロパティの最初の宣言またはオーバーライドから選択されます。T
がclass_typeで、I
がそのclass_typeのインスタンス フィールドを識別する場合:E
の値がnull
場合は、System.NullReferenceException
がスローされます。- それ以外の場合、フィールドが読み取り専用で、フィールドが宣言されているクラスのインスタンス コンストラクターの外部で参照が発生した場合、結果は値、つまり、
E
によって参照されるオブジェクト内のフィールドI
の値になります。 - それ以外の場合、結果は変数になります。つまり、
E
によって参照されるオブジェクト内のフィールドI
。
T
がstruct_typeで、I
がそのstruct_typeのインスタンス フィールドを識別する場合:E
が値である場合、またはフィールドが読み取り専用で、フィールドが宣言されている構造体のインスタンス コンストラクターの外部で参照が発生した場合、結果は値、つまり、E
によって指定された構造体インスタンス内のフィールドI
の値になります。- それ以外の場合、結果は変数になります。つまり、
E
によって指定された構造体インスタンス内のフィールドI
。
I
がインスタンス イベントを識別する場合:- イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言され (§15.8.1)、参照が
a +=
演算子または-=
演算子の左側として発生しない場合、E.I
はインスタンス フィールドI
とまったく同じように処理されます。 - それ以外の場合、結果は、
E
の関連するインスタンス式を使用したイベント アクセスになります。
- イベントが宣言されているクラスまたは構造体内で参照が発生し、イベントが event_accessor_declarations なしで宣言され (§15.8.1)、参照が
- 最初に、
- それ以外の場合は、拡張メソッドの呼び出しとして
E.I
を処理しようとしました (§12.8.10.3)。 これが失敗した場合、E.I
は無効なメンバー参照であり、バインド時エラーが発生します。
12.8.7.2 同一の単純名と型名
フォーム E.I
のメンバー アクセスで、 E
が単一の識別子であり、simple_nameとしてのE
の意味 (§12.8.4) が定数、フィールド、プロパティ、ローカル変数、またはtype_nameとしてのE
の意味と同じ型 (§7.8.1) である場合は、E
の両方の意味が許可されます。 E.I
のメンバー参照はあいまいになることはありません。どちらの場合も、I
は必ず型E
のメンバーである必要があるためです。 言い換えると、この規則は、コンパイル時エラーが発生した静的メンバーと入れ子になった型の E
へのアクセスを許可するだけです。
例:
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() => new Color(...); } class A { public «Color» Color; // Field Color of type Color void F() { Color = «Color».Black; // Refers to Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { «Color» c = «Color».White; // Refers to Color.White static member } }
公開目的でのみ、
A
クラス内では、Color
型を参照するColor
識別子が«...»
で区切られ、Color
フィールドを参照する識別子は区切りません。end の例
12.8.8 Null 条件付きメンバー アクセス
null_conditional_member_accessはmember_accessの条件付きバージョン (§12.8.7) であり、結果の種類がvoid
場合はバインド時エラーです。 結果の型が void
可能性がある null 条件式については、(§12.8.11 を参照してください)。
null_conditional_member_accessは、primary_expressionの後に 2 つのトークン "?
" と ".
" が続き、その後に省略可能なtype_argument_listを持つ identifier、その後に 0 個以上のdependent_accessが続き、いずれも null_forgiving_operator で割り当てることができる 1 つ以上のdependent_accessで構成されます。
null_conditional_member_access
: primary_expression '?' '.' identifier type_argument_list?
(null_forgiving_operator? dependent_access)*
;
dependent_access
: '.' identifier type_argument_list? // member access
| '[' argument_list ']' // element access
| '(' argument_list? ')' // invocation
;
null_conditional_projection_initializer
: primary_expression '?' '.' identifier type_argument_list?
;
null_conditional_member_access式E
は、P?.A
形式です。 E
の意味は次のように決定されます。
P
の型が null 許容値型の場合:T
P.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₁
P
が評価null
場合、A₀
もA₁
も評価されません。 式が null_conditional_member_access または null_conditional_element_access §12.8.13 操作のシーケンスである場合も同様です。end note
null_conditional_projection_initializerはnull_conditional_member_accessの制限であり、同じセマンティクスを持っています。 匿名オブジェクト作成式 (§12.8.17.7) のプロジェクション初期化子としてのみ発生します。
12.8.9 Null を許容する式
12.8.9.1 全般
null を許容する式の値、型、分類 (§12.2) とセーフ コンテキスト (§16.4.12) は、その primary_expressionの値、型、分類、およびセーフ コンテキストです。
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
注: 後置の null 許容演算子とプレフィックス論理否定演算子 (§12.9.4) は、同じ字句トークン (!
) で表されますが、異なります。 オーバーライドできるのは後者 (§15.10) だけです。null 許容演算子の定義は修正されます。 end note
null を許容する演算子を同じ式に複数回適用すると、コンパイル時エラーになります。ただし、かっこの間に入ります。
例: 次はすべて無効です。
var p = q!!; // error: applying null_forgiving_operator more than once var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)
end の例
このサブクラウスの残りの部分と次の兄弟サブクラウゼは、条件付きで規範的です。
静的 null 状態分析 (§8.9.5) を実行するコンパイラは、次の仕様に準拠する必要があります。
null 許容演算子は、コンパイル時の擬似演算であり、コンパイラの静的 null 状態分析を通知するために使用されます。 これには、式が null 値 許容されるというコンパイラの判断をオーバーライドし;null 許容に関連する警告を発行するコンパイラをオーバーライドするという 2 つの用途があります。
コンパイラの静的 null 状態分析で警告が生成されない式に null 許容演算子を適用してもエラーではありません。
12.8.9.2 "null" の判定をオーバーライドする
状況によっては、コンパイラの静的 null 状態分析によって、式の null 状態が null maybe null であると判断され 他の情報が式を null にできないことが示された場合に診断警告を発行することがあります。 このような式に null を許容する演算子を適用すると、コンパイラの静的な null 状態分析に、null 状態が null でないことが通知されます;これにより、診断警告が回避され、進行中の分析が通知される可能性があります。
例: 次の点を考慮してください。
#nullable enable public static void M() { Person? p = Find("John"); // returns Person? if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); // p can't be null } } public static bool IsValid(Person? person) => person != null && person.Name != null;
IsValid
がtrue
を返す場合、p
を安全に逆参照してName
プロパティにアクセスできます。また、!
を使用して "null 値の逆参照" 警告を抑制できます。end の例
例: null を許容する演算子は慎重に使用する必要があります。次の点を考慮してください。
#nullable enable int B(int? x) { int y = (int)x!; // quash warning, throw at runtime if x is null return y; }
ここでは、null を許容する演算子が値型に適用され、
x
の警告が破棄されます。 ただし、実行時にx
がnull
された場合、null
はint
にキャストできないため、例外がスローされます。end の例
12.8.9.3 他の null 分析警告のオーバーライド
maybe null をオーバーライドすることに加えて上記のような決定は、式に 1 つ以上の警告が必要であるというコンパイラの静的 null 状態分析の決定をオーバーライドすることが必要なその他の状況がある場合があります。 このような式要求に null を許容する演算子を適用すると、コンパイラは式に対して警告を発行しません。 これに対して、コンパイラは警告を発行しないことを選択し、さらに分析を変更することもできます。
例: 次の点を考慮してください。
#nullable enable public static void Assign(out string? lv, string? rv) { lv = rv; } public string M(string? t) { string s; Assign(out s!, t ?? "«argument was null»"); return s; }
メソッド
Assign
のパラメーターの型 (lv
&rv
) はstring?
され、lv
は出力パラメーターであり、単純な割り当てが実行されます。メソッド
M
、Assign
の出力パラメーターとして、string
型の変数s
を渡します。s
は null 許容変数ではないため、コンパイラによって警告が発行されます。Assign
の 2 番目の引数を null にできないことを考えると、null を許容する演算子を使用して警告を破棄します。end の例
条件付きで規範的なテキストの末尾。
12.8.10 呼び出し式
12.8.10.1 全般
invocation_expressionは、メソッドを呼び出すために使用されます。
invocation_expression
: primary_expression '(' argument_list? ')'
;
primary_expressionは、delegate_typeがある場合にのみ、null_forgiving_expressionになる可能性があります。
次の少なくとも 1 つが保持されている場合、 invocation_expression は動的にバインドされます (§12.3.3)。
- primary_expressionにはコンパイル時の型
dynamic
があります。 - 省略可能な argument_list の少なくとも 1 つの引数にコンパイル時の型が
dynamic
。
この場合、コンパイラは invocation_expression を dynamic
型の値として分類します。 invocation_expressionの意味を判断するための以下の規則は、コンパイル時の型の代わりに実行時の型を使用して実行時に適用され、primary_expressionの型とコンパイル時の型がdynamic
引数の代わりに適用されます。 primary_expressionにコンパイル時の型dynamic
がない場合、メソッドの呼び出しでは、§12.6.5 で説明されているように、コンパイル時のチェックが制限されます。
invocation_expressionのprimary_expressionは、メソッド グループまたはdelegate_typeの値である必要があります。 primary_expressionがメソッド グループの場合、invocation_expressionはメソッド呼び出しです (§12.8.10.2)。 primary_expressionがdelegate_typeの値である場合、invocation_expressionはデリゲート呼び出しです (§12.8.10.4)。 primary_expressionがメソッド グループでもdelegate_typeの値でもない場合は、バインド時エラーが発生します。
省略可能な argument_list (§12.6.2) は、メソッドのパラメーターの値または変数参照を提供します。
invocation_expressionを評価した結果は、次のように分類されます。
- invocation_expressionが returns-no-value メソッド (§15.6.1) または returns-no-value デリゲートを呼び出した場合、結果は何もありません。 何も分類されていない式は、 statement_expression (§13.7) または lambda_expression の本文 (§12.19) のコンテキストでのみ許可されます。 それ以外の場合は、バインド時エラーが発生します。
- それ以外の場合、 invocation_expression が returns by-ref メソッド (§15.6.1) または returns by-ref デリゲートを呼び出す場合、結果はメソッドまたはデリゲートの戻り値の型に関連付けられた型を持つ変数になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型
T
の場合、関連付けられている型は、T
で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。 - それ以外の場合、 invocation_expression は戻り値によるメソッド (§15.6.1) を呼び出すか、値によるデリゲートを返します。結果は、メソッドまたはデリゲートの戻り値の型に関連付けられた値になります。 呼び出しがインスタンス メソッドの場合、受信側がクラス型
T
の場合、関連付けられている型は、T
で開始し、基底クラスを検索するときに見つかったメソッドの最初の宣言またはオーバーライドから選択されます。
12.8.10.2 メソッドの呼び出し
メソッド呼び出しの場合、invocation_expressionのprimary_expressionはメソッド グループである必要があります。 メソッド グループは、呼び出す 1 つのメソッド、または呼び出す特定のメソッドを選択するオーバーロードされたメソッドのセットを識別します。 後者の場合、呼び出す特定のメソッドの決定は、 argument_listの引数の型によって提供されるコンテキストに基づきます。
フォーム M(A)
のメソッド呼び出しのバインド時処理 ( M
はメソッド グループ (場合によっては type_argument_listを含む) であり、 A
は省略可能な argument_listであり、次の手順で構成されます。
- メソッド呼び出しの候補メソッドのセットが構築されます。 メソッド グループ
M
に関連付けられているメソッドF
ごとに、次の手順を実行します。F
が非ジェネリックの場合、F
は次の場合に候補になります。M
には型引数リストがありません。F
は、A
(§12.6.4.2) に適用されます。
F
がジェネリックで、M
に型引数リストがない場合、F
は次の場合に候補になります。F
がジェネリックで、M
に型引数リストが含まれている場合、F
は次の場合に候補になります。
- 候補メソッドのセットは、最も派生した型のメソッドのみを含むよう縮小されます。セット内の
C.F
メソッドごとに、C
はメソッドF
が宣言されている型であり、基本型のC
で宣言されているすべてのメソッドがセットから削除されます。 さらに、C
がobject
以外のクラス型の場合、インターフェイス型で宣言されているすべてのメソッドがセットから削除されます。注: この後者の規則は、メソッド グループが、
object
以外の有効な基底クラスと空でない有効なインターフェイス セットを持つ型パラメーターに対するメンバー参照の結果である場合にのみ有効です。 end note - 結果の候補メソッドのセットが空の場合、次の手順に沿った処理は中止され、代わりに拡張メソッド呼び出しとして呼び出しを処理しようとします (§12.8.10.3)。 これが失敗した場合は、該当するメソッドが存在せず、バインド時エラーが発生します。
- 候補メソッドのセットの最適なメソッドは、 §12.6.4 のオーバーロード解決規則を使用して識別されます。 1 つの最適なメソッドを識別できない場合、メソッドの呼び出しがあいまいになり、バインド時エラーが発生します。 オーバーロード解決を実行する場合、ジェネリック メソッドのパラメーターは、対応するメソッド型パラメーターの型引数 (指定または推論) を置き換えた後に考慮されます。
上記の手順でバインド時にメソッドを選択して検証すると、実際の実行時呼び出しは、 §12.6.6 で説明されている関数メンバー呼び出しの規則に従って処理されます。
注: 上記の解決規則の直感的な効果は次のとおりです。メソッド呼び出しによって呼び出された特定のメソッドを検索するには、メソッド呼び出しによって示される型から開始し、該当する、アクセス可能な、オーバーライドされていないメソッド宣言が少なくとも 1 つ見つかるまで継承チェーンを続行します。 次に、その型で宣言されている、アクセス可能な非オーバーライド メソッドのセットに対して型の推論とオーバーロードの解決を実行し、選択されたメソッドを呼び出します。 メソッドが見つからなかった場合は、代わりに拡張メソッド呼び出しとして呼び出しを処理してみてください。 end note
12.8.10.3 拡張メソッドの呼び出し
いずれかのフォームのメソッド呼び出し (§12.6.6.2) で
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
呼び出しの通常の処理で該当するメソッドが見つからない場合は、拡張メソッド呼び出しとしてコンストラクトを処理しようとします。 «expr» またはいずれかの «args» にコンパイル時の型 dynamic
がある場合、拡張メソッドは適用されません。
目的は、対応する静的メソッド呼び出しを実行できるように、最適な type_nameC
を見つけることです。
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
拡張メソッドCᵢ.Mₑ
は、次の場合利用できません。
Cᵢ
は、非ジェネリックで入れ子になっていないクラスですMₑ
の名前は identifier ですMₑ
はアクセス可能であり、上記のように静的メソッドとして引数に適用される場合に適用されます- expr から
Mₑ
の最初のパラメーターの型への暗黙的な ID、参照、またはボックス化の変換が存在します。
C
の検索は次のように進みます。
- 最も近い外側の名前空間宣言から始めて、外側の名前空間宣言を続け、それを含むコンパイル単位で終わるまで、拡張メソッドの候補セットを見つけようとします。
- 指定された名前空間またはコンパイル ユニットに、対象となる拡張メソッドが
Mₑ
Cᵢ
非ジェネリック型宣言が直接含まれている場合、それらの拡張メソッドのセットが候補セットになります。 - 指定された名前空間またはコンパイル ユニットの名前空間ディレクティブを使用してインポートされた名前空間に、
Mₑ
対象の拡張メソッドを含むCᵢ
非ジェネリック型宣言が直接含まれている場合、それらの拡張メソッドのセットが候補セットになります。
- 指定された名前空間またはコンパイル ユニットに、対象となる拡張メソッドが
- 外側の名前空間宣言またはコンパイル 単位で候補セットが見つからない場合は、コンパイル時エラーが発生します。
- それ以外の場合は、 §12.6.4 で説明されているように、オーバーロードの解決が候補セットに適用されます。 最適な方法が 1 つも見つからない場合は、コンパイル時エラーが発生します。
C
は、最適なメソッドが拡張メソッドとして宣言される型です。
C
をターゲットとして使用すると、メソッド呼び出しは静的メソッド呼び出しとして処理されます (§12.6.6)。
注: インスタンス メソッドの呼び出しとは異なり、 expr が null 参照に評価されるときに例外はスローされません。 代わりに、この
null
値は、通常の静的メソッド呼び出しを介して行われるように拡張メソッドに渡されます。 このような呼び出しに応答する方法は、拡張メソッドの実装によって決められます。 end note
上記の規則は、インスタンス メソッドが拡張メソッドよりも優先されることを意味し、内部名前空間宣言で使用できる拡張メソッドは、外側の名前空間宣言で使用できる拡張メソッドよりも優先され、名前空間で直接宣言された拡張メソッドは、using 名前空間ディレクティブを使用して同じ名前空間にインポートされた拡張メソッドよりも優先されることを意味します。
例:
public static class E { public static void F(this object obj, int i) { } public static void F(this object obj, string s) { } } class A { } class B { public void F(int i) { } } class C { public void F(object obj) { } } class X { static void Test(A a, B b, C c) { a.F(1); // E.F(object, int) a.F("hello"); // E.F(object, string) b.F(1); // B.F(int) b.F("hello"); // E.F(object, string) c.F(1); // C.F(object) c.F("hello"); // C.F(object) } }
この例では、
B
のメソッドが最初の拡張メソッドよりも優先され、C
のメソッドが両方の拡張メソッドよりも優先されます。public static class C { public static void F(this int i) => Console.WriteLine($"C.F({i})"); public static void G(this int i) => Console.WriteLine($"C.G({i})"); public static void H(this int i) => Console.WriteLine($"C.H({i})"); } namespace N1 { public static class D { public static void F(this int i) => Console.WriteLine($"D.F({i})"); public static void G(this int i) => Console.WriteLine($"D.G({i})"); } } namespace N2 { using N1; public static class E { public static void F(this int i) => Console.WriteLine($"E.F({i})"); } class Test { static void Main(string[] args) { 1.F(); 2.G(); 3.H(); } } }
この例の出力は次のとおりです。
E.F(1) D.G(2) C.H(3)
D.G
はC.G
よりも優先され、E.F
はD.F
とC.F
の両方よりも優先されます。end の例
12.8.10.4 デリゲート呼び出し
デリゲート呼び出しの場合、invocation_expressionのprimary_expressionはdelegate_typeの値である必要があります。 さらに、delegate_typeがdelegate_typeと同じパラメーター リストを持つ関数メンバーであると考えると、invocation_expressionのargument_listに関して、delegate_typeを適用 (§12.6.4.2) する必要があります。
フォーム D(A)
のデリゲート呼び出しの実行時処理 (D
はdelegate_typeのprimary_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_access や null_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_access に delegate_typeがある場合にのみ含まれます。
null_conditional_invocation_expression式E
はP?A
形式です。A
は構文上同等のnull_conditional_member_accessまたはnull_conditional_element_accessの残りの部分であるため、A
は.
または[
で始まります。 PA
P
とA
の結び付きを示しましょう。
E
がstatement_expressionとして発生した場合、E
の意味は statement の意味と同じです:
if ((object)P != null) PA
ただし、 P
は 1 回だけ評価されます。
E
がanonymous_function_bodyまたはmethod_bodyとして発生する場合、E
の意味はその分類によって異なります。
E
が何も分類されない場合、その意味は block の意味と同じです:{ if ((object)P != null) PA; }
ただし、
P
は 1 回だけ評価されます。それ以外の場合、
E
の意味は、 block の意味と同じです:{ return E; }
さらに、このブロックの意味は
E
がnull_conditional_member_access (§12.8.8) またはnull_conditional_element_access (§12.8.13) と構文的に等しいかどうかによって異なります。
12.8.12 要素アクセス
12.8.12.1 全般
element_accessは、primary_no_array_creation_expression、"[
" トークン、argument_list、その後に "]
" トークンで構成されます。 argument_listは、コンマで区切られた 1 つ以上の argument で構成されます。
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
element_accessのargument_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_access を dynamic
型の値として分類します。 element_accessの意味を判断するための以下の規則は、コンパイル時の型がdynamic
primary_no_array_creation_expression式とargument_list式のコンパイル時型ではなく、実行時の型を使用して実行時に適用されます。 primary_no_array_creation_expressionにコンパイル時の型dynamic
がない場合、要素アクセスは、§12.6.5 で説明されているように、制限付きのコンパイル時チェックを受けます。
element_accessのprimary_no_array_creation_expressionがarray_typeの値である場合、element_accessは配列アクセスです (§12.8.12.2)。 それ以外の場合、 primary_no_array_creation_expression は、1 つ以上のインデクサー メンバーを持つクラス、構造体、またはインターフェイス型の変数または値になります。その場合、 element_access はインデクサー アクセス (§12.8.12.3)。
12.8.12.2 配列アクセス
配列アクセスの場合、element_accessのprimary_no_array_creation_expressionはarray_typeの値である必要があります。 さらに、配列アクセスの argument_list には、名前付き引数を含めることはできません。 argument_list内の式の数は、array_typeのランクと同じで、各式は、int
、uint
、long
、またはulong,
の型であるか、またはこれらの型の 1 つ以上に暗黙的に変換できる必要があります。
配列アクセスを評価した結果は、配列の要素型の変数、つまり、 argument_list内の式の値によって選択された配列要素です。
P
がarray_typeのprimary_no_array_creation_expressionであり、A
がargument_listであるフォーム P[A]
の配列アクセスの実行時処理は、次の手順で構成されます。
P
が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。- argument_listのインデックス式は、左から右の順に評価されます。 各インデックス式の評価の後、
int
、uint
、long
、ulong
のいずれかの型への暗黙的な変換 (§10.2) が実行されます。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 たとえば、インデックス式が型short
の場合、short
からint
への暗黙的な変換と、short
からlong
への暗黙的な変換が可能であるため、int
への暗黙的な変換が実行されます。 インデックス式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上のインデックス式は評価されません。それ以上の手順は実行されません。 P
の値が有効であることが確認されます。P
の値がnull
場合、System.NullReferenceException
がスローされ、それ以上の手順は実行されません。- argument_list内の各式の値は、
P
によって参照される配列インスタンスの各次元の実際の境界に対してチェックされます。 1 つ以上の値が範囲外の場合、System.IndexOutOfRangeException
がスローされ、それ以上の手順は実行されません。 - インデックス式によって指定された配列要素の位置が計算され、この場所が配列アクセスの結果になります。
12.8.12.3 インデクサー アクセス
インデクサー アクセスの場合、element_accessのprimary_no_array_creation_expressionはクラス、構造体、またはインターフェイス型の変数または値であり、この型は、element_accessのargument_listに適用できる 1 つ以上のインデクサーを実装する必要があります。
P
はクラス、構造体、またはインターフェイス型のT
のprimary_no_array_creation_expressionであり、A
はargument_listであるフォーム P[A]
のインデクサー アクセスのバインド時処理は、次の手順で構成されます。
T
によって提供されるインデクサーのセットが構築されます。 セットは、T
で宣言されているすべてのインデクサー、または宣言をオーバーライドせず、現在のコンテキスト (§7.5) でアクセスできるT
の基本型で構成されます。- このセットは、他のインデクサーによって適用され、非表示にならないインデクサーに縮小されます。 次の規則は、セット内の各インデクサー
S.I
に適用されます。ここで、S
はインデクサーI
が宣言されている型です。 - 結果の候補インデクサーのセットが空の場合、適用可能なインデクサーは存在せず、バインド時エラーが発生します。
- 候補インデクサーのセットの最適なインデクサーは、 §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.12) であり、結果の型がvoid
場合はバインディング時間エラーです。 結果の型が void
可能性がある null 条件式については、(§12.8.11 を参照してください)。
null_conditional_element_access式E
はP?[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₁]
P
が評価null
A₀
またはA₁
のどちらも評価されません。 式が null_conditional_element_access または null_conditional_member_access §12.8.8 操作のシーケンスである場合も同様です。end note
12.8.14 このアクセス
this_accessはキーワード this
で構成されます。
this_access
: 'this'
;
this_accessは、インスタンス コンストラクター、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはファイナライザーのブロックでのみ許可されます。 次のいずれかの意味があります。
- クラスのインスタンス コンストラクター内のprimary_expressionで
this
を使用すると、値として分類されます。 値の型は、使用法が発生するクラスのインスタンス型 (§15.3.2) であり、値は構築されるオブジェクトへの参照です。 - クラスのインスタンス メソッドまたはインスタンス アクセサー内のprimary_expressionで
this
を使用すると、値として分類されます。 値の型は、使用法が発生するクラスのインスタンス型 (§15.3.2) であり、値はメソッドまたはアクセサーが呼び出されたオブジェクトへの参照です。 - 構造体のインスタンス コンストラクター内のprimary_expressionで
this
を使用すると、変数として分類されます。 変数の型は、使用が行われる構造体のインスタンス型 (§15.3.2) であり、変数は構築される構造体を表します。- コンストラクター宣言にコンストラクター初期化子がない場合、
this
変数は構造体型の出力パラメーターとまったく同じように動作します。 特に、これは、インスタンス コンストラクターのすべての実行パスで変数が確実に割り当てられることを意味します。 - それ以外の場合、
this
変数は構造体型のref
パラメーターとまったく同じように動作します。 特に、これは変数が最初に割り当てられたと見なされることを意味します。
- コンストラクター宣言にコンストラクター初期化子がない場合、
- 構造体のインスタンス メソッドまたはインスタンス アクセサー内のprimary_expressionで
this
を使用すると、変数として分類されます。 変数の型は、使用が行われる構造体のインスタンス型 (§15.3.2) です。- メソッドまたはアクセサーが反復子 (§15.14) または非同期関数 (§15.15) でない場合、
this
変数はメソッドまたはアクセサーが呼び出された構造体を表します。- 構造体が
readonly struct
の場合、this
変数は構造体型の入力パラメーターとまったく同じように動作します。 - それ以外の場合、
this
変数は構造体型のref
パラメーターとまったく同じように動作します。
- 構造体が
- メソッドまたはアクセサーが反復子または非同期関数の場合、
this
変数はメソッドまたはアクセサーが呼び出された構造体の コピー を表し、構造体型の value パラメーターとまったく同じように動作します。
- メソッドまたはアクセサーが反復子 (§15.14) または非同期関数 (§15.15) でない場合、
上記のコンテキスト以外のコンテキストでprimary_expressionでthis
を使用すると、コンパイル時エラーになります。 特に、静的メソッド、静的プロパティ アクセサー、またはフィールド宣言のvariable_initializerでthis
を参照することはできません。
12.8.15 ベース アクセス
base_accessは、キーワード ベースの後に ".
" トークンと識別子と省略可能なtype_argument_list、または角かっこで囲まれたargument_listで構成されます。
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
base_accessは、現在のクラスまたは構造体の同様の名前付きメンバーによって非表示になっている基底クラスのメンバーにアクセスするために使用されます。 base_accessは、インスタンス コンストラクター、インスタンス メソッド、インスタンス アクセサー (§12.2.1)、またはファイナライザーの本体でのみ許可されます。 クラスまたは構造体で base.I
が発生した場合、そのクラスまたは構造体の基底クラスのメンバーを示します。 同様に、クラスで base[E]
が発生した場合、適用可能なインデクサーが基底クラスに存在する必要があります。
バインド時に、フォーム base.I
とbase[E]
のbase_access式は、((B)this).I
と((B)this)[E]
が記述された場合とまったく同じように評価されます。ここで、B
はコンストラクトが発生するクラスまたは構造体の基底クラスです。 したがって、 base.I
と base[E]
は this.I
と this[E]
に対応します。ただし、 this
は基底クラスのインスタンスと見なされます。
base_accessが仮想関数メンバー (メソッド、プロパティ、またはインデクサー) を参照すると、実行時に呼び出す関数メンバーの決定 (§12.6.6) が変更されます。 呼び出される関数メンバーは、B
に関して関数メンバーの最も派生した実装 (§15.6.4) を見つけることによって決定されます (基本以外のアクセスでは通常と同様に、this
の実行時の型に関してではなく)。 したがって、仮想関数メンバーのオーバーライド内で、 base_access を使用して、関数メンバーの継承された実装を呼び出すことができます。 base_accessによって参照される関数メンバーが抽象である場合は、バインド時エラーが発生します。
注:
this
とは異なり、base
自体は式ではありません。 これは、 base_access または constructor_initializer (§15.11.2) のコンテキストでのみ使用されるキーワードです。 end note
12.8.16 後置インクリメントおよびデクリメント演算子
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
後置インクリメントまたはデクリメント演算のオペランドは、変数、プロパティ アクセス、またはインデクサー アクセスとして分類される式でなければなりません。 演算の結果は、オペランドと同じ型の値になります。
primary_expressionにコンパイル時の型dynamic
がある場合、演算子は動的にバインドされます (§12.3.3)、post_increment_expressionまたはpost_decrement_expressionにはコンパイル時の型dynamic
があり、primary_expressionの実行時の型を使用して次の規則が実行時に適用されます。
後置インクリメントまたはデクリメント演算のオペランドがプロパティまたはインデクサー アクセスである場合、プロパティまたはインデクサーには get アクセサーと set アクセサーの両方が含まれます。 そうでない場合は、バインド時エラーが発生します。
単項演算子のオーバーロード解決 (§12.4.4) を適用して、特定の演算子の実装を選択します。 定義済みの ++
演算子と --
演算子は、 sbyte
、 byte
、 short
、 ushort
、 int
、 uint
、 long
、 ulong
、 char
、 float
、 double
、 decimal
、および任意の列挙型に対して存在します。 定義済みの ++
演算子は、オペランドに 1
を追加することによって生成された値を返し、定義済みの --
演算子は、オペランドから 1
を減算することによって生成された値を返します。 チェックされたコンテキストでは、この加算または減算の結果が結果型の範囲外で、結果の型が整数型または列挙型の場合、 System.OverflowException
がスローされます。
選択した単項演算子の戻り値の型から primary_expressionの型への暗黙的な変換が必要です。それ以外の場合は、コンパイル時エラーが発生します。
フォーム x++
または x--
の後置インクリメントまたはデクリメント操作の実行時処理は、次の手順で構成されます。
x
が変数として分類される場合:x
は、変数を生成するために評価されます。x
の値が保存されます。x
の保存された値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。- 演算子によって返される値は、
x
の型に変換され、x
の以前の評価によって指定された場所に格納されます。 x
の保存された値が操作の結果になります。
x
がプロパティまたはインデクサー アクセスとして分類されている場合:- インスタンス式 (
x
がstatic
されていない場合) と、x
に関連付けられている引数リスト (x
がインデクサー アクセスの場合) が評価され、その結果は後続の get アクセサー呼び出しおよび set アクセサー呼び出しで使用されます。 x
の get アクセサーが呼び出され、戻り値が保存されます。x
の保存された値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。- 演算子によって返される値は
x
の型に変換され、x
の set アクセサーは値引数としてこの値を使用して呼び出されます。 x
の保存された値が操作の結果になります。
- インスタンス式 (
++
演算子と--
演算子では、プレフィックス表記もサポートされています (§12.9.6)。 x++
またはx--
の結果は、操作のx
前値ですが、++x
または--x
の結果は操作x
後値です。 どちらの場合も、 x
自体は操作後に同じ値を持ちます。
演算子 ++
または演算子 --
実装は、後置表記またはプレフィックス表記を使用して呼び出すことができます。 2 つの表記に対して個別の演算子を実装することはできません。
12.8.17 新しい演算子
12.8.17.1 全般
new
演算子は、型の新しいインスタンスを作成するために使用されます。
新しい式には、次の 3 つの形式があります。
- オブジェクト作成式と匿名オブジェクト作成式は、クラス型と値型の新しいインスタンスを作成するために使用されます。
- 配列作成式は、配列型の新しいインスタンスを作成するために使用されます。
- デリゲート作成式は、デリゲート型のインスタンスを取得するために使用されます。
new
演算子は、型のインスタンスの作成を意味しますが、必ずしもメモリの割り当てを意味するとは限りません。 特に、値型のインスタンスでは、存在する変数を超える追加のメモリは必要ありません。また、 new
を使用して値型のインスタンスを作成するときに割り当ては行われません。
注: デリゲート作成式では、常に新しいインスタンスが作成されるわけではありません。 式がメソッド グループ変換 (§10.8) または匿名関数変換 (§10.7) と同じ方法で処理されると、既存のデリゲート インスタンスが再利用される可能性があります。 end note
12.8.17.2 オブジェクト作成式
object_creation_expressionは、class_typeまたはvalue_typeの新しいインスタンスを作成するために使用されます。
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
;
object_or_collection_initializer
: object_initializer
| collection_initializer
;
object_creation_expressionの型は、class_type、value_type、またはtype_parameterである必要があります。 typeを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 で説明されているように、コンパイル時のチェックが制限されます。
T
がclass_typeまたはvalue_typeで、A
が省略可能なargument_listである新しいT(A)
のobject_creation_expressionのバインド時処理は、次の手順で構成されます。
T
がvalue_typeであり、A
が存在しない場合:- object_creation_expressionは、既定のコンストラクター呼び出しです。 object_creation_expressionの結果は、
T
型の値です。つまり、§8.3.3 で定義されているT
の既定値。
- object_creation_expressionは、既定のコンストラクター呼び出しです。 object_creation_expressionの結果は、
- それ以外の場合、
T
が type_parameter であり、A
が存在しない場合:T
に値型制約またはコンストラクター制約 (§15.2.5) が指定されていない場合は、バインド時エラーが発生します。- object_creation_expressionの結果は、型パラメーターがバインドされているランタイム型の値、つまり、その型の既定のコンストラクターを呼び出した結果です。 実行時の型には、参照型または値型を指定できます。
- それ以外の場合、
T
が class_type または struct_typeの場合:T
が抽象class_typeまたは静的class_typeの場合は、コンパイル時エラーが発生します。- 呼び出すインスタンス コンストラクターは、 §12.6.4 のオーバーロード解決規則を使用して決定されます。 候補インスタンス コンストラクターのセットは、
T
で宣言されているすべてのアクセス可能なインスタンス コンストラクターで構成されます。これは A (§12.6.4.2 に適用できます。 候補のインスタンス コンストラクターのセットが空の場合、または 1 つの最適なインスタンス コンストラクターを識別できない場合は、バインド時エラーが発生します。 - object_creation_expressionの結果は、
T
型の値、つまり、上記の手順で決定されたインスタンス コンストラクターを呼び出すことによって生成される値です。 - それ以外の場合、 object_creation_expression は無効であり、バインド時エラーが発生します。
object_creation_expressionが動的にバインドされている場合でも、コンパイル時の型は引き続きT
。
フォームの新しいT(A)
のobject_creation_expressionの実行時処理 (T
がclass_typeまたはstruct_typeで、A
が省略可能なargument_listである場合) は、次の手順で構成されます。
T
がclass_typeの場合:T
がstruct_typeの場合:T
型のインスタンスは、一時ローカル変数を割り当てることによって作成されます。 struct_typeのインスタンス コンストラクターは、作成するインスタンスの各フィールドに値を確実に割り当てる必要があるため、一時変数の初期化は必要ありません。- インスタンス コンストラクターは、関数メンバー呼び出しの規則に従って呼び出されます (§12.6.6)。 新しく割り当てられたインスタンスへの参照がインスタンス コンストラクターに自動的に渡され、このコンストラクター内からインスタンスにアクセスできます。
12.8.17.3 オブジェクト初期化子
オブジェクト初期化子は、オブジェクトの 0 個以上のフィールド、プロパティ、またはインデックス付き要素の値を指定します。
object_initializer
: '{' member_initializer_list? '}'
| '{' member_initializer_list ',' '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: initializer_target '=' initializer_value
;
initializer_target
: identifier
| '[' argument_list ']'
;
initializer_value
: expression
| object_or_collection_initializer
;
オブジェクト初期化子は、メンバー初期化子のシーケンスで構成され、 {
トークンと }
トークンで囲み、コンマで区切られます。 各 member_initializer は、初期化のターゲットを指定する必要があります。 identifierは、初期化するオブジェクトのアクセス可能なフィールドまたはプロパティに名前を付けますが、角かっこで囲まれたargument_listは、初期化するオブジェクトのアクセス可能なインデクサーの引数を指定する必要があります。 オブジェクト初期化子が同じフィールドまたはプロパティに対して複数のメンバー初期化子を含めるのはエラーです。
注: オブジェクト初期化子は同じフィールドまたはプロパティを複数回設定することは許可されていませんが、インデクサーにはこのような制限はありません。 オブジェクト初期化子には、インデクサーを参照する複数の初期化子ターゲットを含め、同じインデクサー引数を複数回使用することもできます。 end note
各 initializer_target の後に等号と、式、オブジェクト初期化子、またはコレクション初期化子が続きます。 オブジェクト初期化子内の式で、初期化中の新しく作成されたオブジェクトを参照することはできません。
等号の後に式を指定するメンバー初期化子は、ターゲットへの代入 (§12.21.2) と同じ方法で処理されます。
等号の後にオブジェクト初期化子を指定するメンバー初期化子は、 nested オブジェクト初期化子、つまり埋め込みオブジェクトの初期化です。 フィールドまたはプロパティに新しい値を割り当てる代わりに、入れ子になったオブジェクト初期化子の割り当ては、フィールドまたはプロパティのメンバーへの割り当てとして扱われます。 入れ子になったオブジェクト初期化子は、値型のプロパティや、値型を持つ読み取り専用フィールドには適用できません。
等号の後にコレクション初期化子を指定するメンバー初期化子は、埋め込みコレクションの初期化です。 ターゲット フィールド、プロパティ、またはインデクサーに新しいコレクションを割り当てる代わりに、初期化子で指定された要素がターゲットによって参照されるコレクションに追加されます。 ターゲットは、 §12.8.17.4 で指定された要件を満たすコレクション型である必要があります。
初期化子ターゲットがインデクサーを参照する場合、インデクサーの引数は常に 1 回だけ評価されます。 したがって、引数が使用されていなくても (たとえば、入れ子になった初期化子が空のため)、副作用が評価されます。
例: 次のクラスは、2 つの座標を持つ点を表します。
public class Point { public int X { get; set; } public int Y { get; set; } }
Point
のインスタンスは、次のように作成および初期化できます。Point a = new Point { X = 0, Y = 1 };
これは、次の場合と同じ効果があります。
Point __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;
ここで、
__a
はそれ以外の場合は非表示でアクセスできない一時変数です。次のクラスは、2 つのポイントから作成された四角形と、
Rectangle
インスタンスの作成と初期化を示しています。public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }
Rectangle
のインスタンスは、次のように作成および初期化できます。Rectangle r = new Rectangle { P1 = new Point { X = 0, Y = 1 }, P2 = new Point { X = 2, Y = 3 } };
これは、次の場合と同じ効果があります。
Rectangle __r = new Rectangle(); Point __p1 = new Point(); __p1.X = 0; __p1.Y = 1; __r.P1 = __p1; Point __p2 = new Point(); __p2.X = 2; __p2.Y = 3; __r.P2 = __p2; Rectangle r = __r;
ここで、
__r
、__p1
、および__p2
は、それ以外の場合は非表示でアクセスできない一時的な変数です。
Rectangle
のコンストラクターが 2 つの埋め込みPoint
インスタンスを割り当てる場合は、新しいインスタンスを割り当てるのではなく、埋め込みPoint
インスタンスを初期化するために使用できます。public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }
次のコンストラクトを使用して、新しいインスタンスを割り当てる代わりに、埋め込み
Point
インスタンスを初期化できます。Rectangle r = new Rectangle { P1 = { X = 0, Y = 1 }, P2 = { X = 2, Y = 3 } };
これは、次の場合と同じ効果があります。
Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r;
end の例
12.8.17.4 コレクション初期化子
コレクション初期化子は、コレクションの要素を指定します。
collection_initializer
: '{' element_initializer_list '}'
| '{' element_initializer_list ',' '}'
;
element_initializer_list
: element_initializer (',' element_initializer)*
;
element_initializer
: non_assignment_expression
| '{' expression_list '}'
;
expression_list
: expression
| expression_list ',' expression
;
コレクション初期化子は、要素初期化子のシーケンスで構成され、 {
トークンと }
トークンで囲み、コンマで区切られます。 各要素初期化子は、初期化するコレクション オブジェクトに追加する要素を指定し、 {
トークンと }
トークンで囲まれた式の一覧で構成され、コンマで区切られます。 単一式要素初期化子は中かっこなしで記述できますが、メンバー初期化子のあいまいさを回避するために代入式にすることはできません。 non_assignment_expression運用は、§12.22 で定義されています。
例: コレクション初期化子を含むオブジェクト作成式の例を次に示します。
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
end の例
コレクション初期化子が適用されるコレクション オブジェクトは、 System.Collections.IEnumerable
を実装する型であるか、コンパイル時エラーが発生します。 左から右の順序で指定された各要素に対して、通常のメンバー参照が適用され、 Add
という名前のメンバーが検索されます。 メンバー参照の結果がメソッド グループでない場合は、コンパイル時エラーが発生します。 それ以外の場合は、要素初期化子の式リストを引数リストとしてオーバーロード解決が適用され、コレクション初期化子によって結果のメソッドが呼び出されます。 したがって、コレクション オブジェクトには、各要素初期化子の名前 Add
を持つ適用可能なインスタンスまたは拡張メソッドが含まれている必要があります。
例:次に、名前と電話番号の一覧を持つ連絡先を表すクラスと、
List<Contact>
の作成と初期化を示します。public class Contact { public string Name { get; set; } public List<string> PhoneNumbers { get; } = new List<string>(); } class A { static void M() { var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; } }
と同じ効果を持つ
var __clist = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); __clist.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); __clist.Add(__c2); var contacts = __clist;
ここで、
__clist
、__c1
、および__c2
は、それ以外の場合は非表示でアクセスできない一時的な変数です。end の例
12.8.17.5 配列作成式
array_creation_expressionは、array_typeの新しいインスタンスを作成するために使用されます。
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
最初のフォームの配列作成式は、式リストから個々の式を削除した結果として生じる型の配列インスタンスを割り当てます。
例: 配列作成式
new int[10,20]
はint[,]
型の配列インスタンスを生成し、新しいint[10][,]
配列作成式はint[][,]
型の配列インスタンスを生成します。 end の例
式リスト内の各式は、 int
、 uint
、 long
、または ulong
の型であるか、またはこれらの型の 1 つ以上に暗黙的に変換可能である必要があります。 各式の値によって、新しく割り当てられた配列インスタンス内の対応するディメンションの長さが決まります。 配列次元の長さは負の値でないため、式リスト内で負の値を持つ定数式を持つことはコンパイル時エラーです。
アンセーフ コンテキスト (§23.2) を除き、配列のレイアウトは指定されていません。
最初の形式の配列作成式に配列初期化子が含まれている場合、式リスト内の各式は定数で、式リストで指定されたランクと次元の長さは配列初期化子の長さと一致する必要があります。
2 番目または 3 番目の形式の配列作成式では、指定された配列型またはランク指定子のランクは、配列初期化子のランクと一致する必要があります。 個々の次元の長さは、配列初期化子の対応する入れ子レベルの要素の数から推論されます。 したがって、次の宣言の初期化子式
var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};
と完全に対応します。
var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};
3 番目の形式の配列作成式は、 単純に型指定された配列作成式と呼ばれます。 これは、配列の要素型が明示的に指定されていないが、配列初期化子の式セットの最も一般的な型 (§12.6.3.15) として決定されることを除いて、2 番目の形式に似ています。 多次元配列 (つまり、rank_specifierに少なくとも 1 つのコンマが含まれている配列) の場合、このセットは、入れ子になったarray_initializerで見つかったすべての式で構成されます。
配列初期化子については、 §17.7 で詳しく説明します。
配列作成式を評価した結果は、値、つまり新しく割り当てられた配列インスタンスへの参照として分類されます。 配列作成式の実行時処理は、次の手順で構成されます。
- expression_listのディメンション長式は、左から右の順に評価されます。 各式の評価の後、
int
、uint
、long
、ulong
のいずれかの型への暗黙的な変換 (§10.2) が実行されます。 暗黙的な変換が存在するこのリストの最初の型が選択されます。 式の評価またはその後の暗黙的な変換によって例外が発生した場合、それ以上の式は評価されません。それ以上の手順は実行されません。 - ディメンションの長さの計算値は、次のように検証されます。1 つ以上の値が 0 未満の場合、
System.OverflowException
がスローされ、それ以上の手順は実行されません。 - 指定された次元の長さを持つ配列インスタンスが割り当てられます。 新しいインスタンスを割り当てるのに十分なメモリがない場合は、
System.OutOfMemoryException
がスローされ、それ以上の手順は実行されません。 - 新しい配列インスタンスのすべての要素は、既定値 (§9.3) に初期化されます。
- 配列作成式に配列初期化子が含まれている場合、配列初期化子内の各式が評価され、対応する配列要素に割り当てられます。 評価と代入は、式が配列初期化子に書き込まれる順序で実行されます。つまり、要素はインデックスの順序を増やして初期化され、右端の次元が最初に増加します。 特定の式の評価または対応する配列要素への後続の代入によって例外が発生した場合、それ以上の要素は初期化されません (そのため、残りの要素には既定値が設定されます)。
配列作成式では、配列型の要素を含む配列のインスタンス化が可能ですが、このような配列の要素は手動で初期化する必要があります。
例: ステートメント
int[][] a = new int[100][];
は、
int[]
型の 100 個の要素を持つ 1 次元配列を作成します。 各要素の初期値はnull
。 同じ配列作成式でサブ配列とステートメントをインスタンス化することはできません。int[][] a = new int[100][5]; // Error
の場合、コンパイル時エラーが発生します。 サブ配列のインスタンス化は、次のように手動で実行できます。
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }
end の例
注: 配列の配列に "四角形" 図形がある場合、つまりサブ配列がすべて同じ長さの場合は、多次元配列を使用する方が効率的です。 上記の例では、配列の配列をインスタンス化すると、101 個のオブジェクト (1 つの外側の配列と 100 個のサブ配列) が作成されます。 それに対して、
int[,] a = new int[100, 5];
では、1 つのオブジェクト (2 次元配列) のみが作成され、1 つのステートメントで割り当てが実行されます。
end note
例: 暗黙的に型指定された配列作成式の例を次に示します。
var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,] var d = new[] { 1, "one", 2, "two" }; // Error
最後の式では、
int
もstring
も暗黙的に他の型に変換できないため、コンパイル時エラーが発生します。 この場合は、明示的に型指定された配列作成式を使用する必要があります。たとえば、object[]
する型を指定する必要があります。 または、いずれかの要素を共通の基本型にキャストして、推論される要素型にすることができます。end の例
暗黙的に型指定された配列作成式を匿名オブジェクト初期化子 (§12.8.17.7) と組み合わせて、匿名で型指定されたデータ構造を作成できます。
例:
var contacts = new[] { new { Name = "Chris Smith", PhoneNumbers = new[] { "206-555-0101", "425-882-8080" } }, new { Name = "Bob Harris", PhoneNumbers = new[] { "650-555-0199" } } };
end の例
12.8.17.6 デリゲート作成式
delegate_creation_expressionは、delegate_typeのインスタンスを取得するために使用されます。
delegate_creation_expression
: 'new' delegate_type '(' expression ')'
;
デリゲート作成式の引数は、メソッド グループ、匿名関数、またはコンパイル時の型 dynamic
または delegate_typeのいずれかの値である必要があります。 引数がメソッド グループの場合は、メソッドを識別し、インスタンス メソッドの場合はデリゲートを作成するオブジェクトを識別します。 引数が匿名関数の場合、デリゲート ターゲットのパラメーターとメソッド本体を直接定義します。 引数が値の場合、コピーを作成するデリゲート インスタンスを識別します。
式にコンパイル時の型dynamic
がある場合、delegate_creation_expressionは動的にバインドされ (§12.8.17.6)、式の実行時の型を使用して、以下の規則が実行時に適用されます。 それ以外の場合、ルールはコンパイル時に適用されます。
D
がdelegate_typeで、E
が式である新しいD(E)
のdelegate_creation_expressionのバインド時処理は、次の手順で構成されます。
E
がメソッド グループの場合、デリゲート作成式は、E
からD
へのメソッド グループ変換 (§10.8) と同じ方法で処理されます。E
が匿名関数の場合、デリゲート作成式は、E
からD
への匿名関数変換 (§10.7) と同じ方法で処理されます。E
が値の場合、E
はD
と互換性があり (§20.2)、結果は、E
を呼び出す単一エントリの呼び出しリストを持つ新しく作成されたデリゲートへの参照になります。
D
がdelegate_typeで、E
が式である新しいD(E)
のdelegate_creation_expressionの実行時処理は、次の手順で構成されます。
E
がメソッド グループの場合、デリゲート作成式は、E
からD
へのメソッド グループ変換 (§10.8) として評価されます。E
が匿名関数の場合、デリゲートの作成は、E
からD
への匿名関数変換として評価されます (§10.7)。E
がdelegate_typeの値である場合:E
が評価されます。 この評価によって例外が発生した場合、それ以上の手順は実行されません。E
の値がnull
場合、System.NullReferenceException
がスローされ、それ以上の手順は実行されません。D
デリゲート型の新しいインスタンスが割り当てられます。 新しいインスタンスを割り当てるのに十分なメモリがない場合は、System.OutOfMemoryException
がスローされ、それ以上の手順は実行されません。- 新しいデリゲート インスタンスは、
E
を呼び出す単一エントリ呼び出しリストで初期化されます。
デリゲートの呼び出しリストは、デリゲートがインスタンス化されるときに決定され、デリゲートの有効期間全体にわたって一定のままになります。 つまり、作成されたデリゲートのターゲット呼び出し可能エンティティを変更することはできません。
注: 2 つのデリゲートを組み合わせたり、1 つを別のデリゲートから削除したりすると、新しいデリゲートの結果が得られます。既存のデリゲートの内容は変更されていないことに注意してください。 end note
プロパティ、インデクサー、ユーザー定義演算子、インスタンス コンストラクター、ファイナライザー、または静的コンストラクターを参照するデリゲートを作成することはできません。
例: 前述のように、メソッド グループからデリゲートが作成されると、デリゲートのパラメーター リストと戻り値の型によって、どのオーバーロードされたメソッドを選択するかが決まります。 この例では、
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) => x * x; static double Square(double x) => x * x; }
A.f
フィールドは、2 番目のSquare
メソッドを参照するデリゲートで初期化されます。これは、そのメソッドがパラメーター リストと戻り値の型と完全に一致するためDoubleFunc
。 2 番目のSquare
メソッドが存在しない場合は、コンパイル時エラーが発生します。end の例
12.8.17.7 匿名オブジェクト作成式
anonymous_object_creation_expressionは、匿名型のオブジェクトを作成するために使用されます。
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
;
anonymous_object_initializer
: '{' member_declarator_list? '}'
| '{' member_declarator_list ',' '}'
;
member_declarator_list
: member_declarator (',' member_declarator)*
;
member_declarator
: simple_name
| member_access
| null_conditional_projection_initializer
| base_access
| identifier '=' expression
;
匿名オブジェクト初期化子は匿名型を宣言し、その型のインスタンスを返します。 匿名型は、 object
から直接継承する名前のないクラス型です。 匿名型のメンバーは、型のインスタンスを作成するために使用される匿名オブジェクト初期化子から推論される読み取り専用プロパティのシーケンスです。 具体的には、フォームの匿名オブジェクト初期化子
new {
p₁ =
e₁ ,
p₂ =
e₂ ,
… pv =
ev }
フォームの匿名型を宣言します
class __Anonymous1
{
private readonly «T1» «f1»;
private readonly «T2» «f2»;
...
private readonly «Tn» «fn»;
public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
{
«f1» = «a1»;
«f2» = «a2»;
...
«fn» = «an»;
}
public «T1» «p1» { get { return «f1»; } }
public «T2» «p2» { get { return «f2»; } }
...
public «Tn» «pn» { get { return «fn»; } }
public override bool Equals(object __o) { ... }
public override int GetHashCode() { ... }
}
ここで、各 «Tx» は、対応する式 «ex» の型です。 member_declaratorで使用される式は型を持つ必要があります。 したがって、 member_declarator 内の式が null
または匿名関数のコンパイル時エラーになります。
匿名型の名前とその Equals
メソッドのパラメーターはコンパイラによって自動的に生成され、プログラム テキストでは参照できません。
同じプログラム内で、同じ名前とコンパイル時の型の一連のプロパティを同じ順序で指定する 2 つの匿名オブジェクト初期化子は、同じ匿名型のインスタンスを生成します。
例: この例では
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;
p1
とp2
が同じ匿名型であるため、最後の行の割り当てが許可されます。end の例
匿名型の Equals
メソッドと GetHashcode
メソッドは、 object
から継承されたメソッドをオーバーライドし、プロパティの Equals
と GetHashcode
の観点から定義されます。したがって、同じ匿名型の 2 つのインスタンスが等しい場合は、すべてのプロパティが等しい場合にのみ等しくなります。
メンバー宣言子は、単純な名前 (§12.8.4)、メンバー アクセス (§12.8.7)、null 条件付きプロジェクション初期化子 §12.8.8 または基本アクセス (§12.8.15) に省略できます。 これは、 projection 初期化子 と呼ばれ、同じ名前のプロパティの宣言と割り当ての短縮形です。 具体的には、フォームのメンバー宣言子
«identifier»
、«expr» . «identifier»
、«expr» ? . «identifier»
は、それぞれ次とまったく同じです。
«identifer» = «identifier»
、«identifier» = «expr» . «identifier»
、«identifier» = «expr» ? . «identifier»
したがって、プロジェクション初期化子では、識別子は値と、値が割り当てられているフィールドまたはプロパティの両方を選択します。 直感的に、プロジェクション初期化子は値だけでなく、値の名前も投影します。
12.8.18 typeof 演算子
typeof
演算子は、型のSystem.Type
オブジェクトを取得するために使用されます。
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
| identifier '::' identifier generic_dimension_specifier?
| unbound_type_name '.' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
typeof_expressionの最初の形式は、typeof
キーワードの後にかっこで囲まれた型で構成されます。 このフォームの式の結果は、指定された型の System.Type
オブジェクトです。 特定の型に対して System.Type
オブジェクトは 1 つだけ存在します。 つまり、型 T
の場合、 typeof(T) == typeof(T)
は常に true になります。 型は dynamic
にはできません。
2 番目の形式の typeof_expression は、 typeof
キーワードの後に、かっこで囲まれた unbound_type_nameで構成されます。
注: unbound_type_nameはtype_name (§7.8) とよく似ていますが、unbound_type_nameにはtype_nameにtype_argument_listが含まれるgeneric_dimension_specifierが含まれている点が異なります。 end note
typeof_expressionのオペランドが、unbound_type_nameとtype_nameの両方の文法を満たす一連のトークンである場合(つまり、generic_dimension_specifierもtype_argument_listも含まなかった場合)、トークンのシーケンスはtype_nameと見なされます。 unbound_type_nameの意味は次のように決定されます。
- 各generic_dimension_specifierを、各type_argumentと同じ数のコンマとキーワード
object
を持つtype_argument_listに置き換えることで、トークンのシーケンスをtype_nameに変換します。 - すべての型パラメーター制約を無視しながら、結果の type_nameを評価します。
- unbound_type_nameは、結果として作成された型 (§8.4) に関連付けられた非連結ジェネリック型に解決されます。
型名が null 許容参照型になるというエラーです。
typeof_expressionの結果は、結果のバインドされていないジェネリック型のSystem.Type
オブジェクトです。
3 番目の形式の typeof_expression は、 typeof
キーワードの後に、かっこで囲まれた void
キーワードで構成されます。 このフォームの式の結果は、型が存在しないことを表す System.Type
オブジェクトです。 typeof(void)
によって返される型オブジェクトは、任意の型に対して返される型オブジェクトとは異なります。
注:この特殊な
System.Type
オブジェクトは、言語内のメソッドへのリフレクションを可能にするクラス ライブラリで役立ちます。これらのメソッドは、void
メソッドを含む任意のメソッドの戻り値の型をSystem.Type
のインスタンスと共に表す方法を持つことを望みます。 end note
typeof
演算子は、型パラメーターで使用できます。 型名が null 許容参照型であることがわかっていれば、コンパイル時エラーになります。 結果は、型パラメーターにバインドされたランタイム型の System.Type
オブジェクトです。 実行時の型が null 許容参照型の場合、結果は対応する null 非許容参照型になります。 typeof
演算子は、構築された型またはバインドされていないジェネリック型 (§8.4.4) でも使用できます。 バインドされていないジェネリック型のSystem.Type
オブジェクトは、インスタンス型 (§15.3.2) のSystem.Type
オブジェクトと同じではありません。 インスタンス型は実行時に常に閉じた構築型であるため、その System.Type
オブジェクトは使用中の実行時の型引数に依存します。 一方、バインドされていないジェネリック型には型引数がなく、ランタイム型引数に関係なく同じ System.Type
オブジェクトが生成されます。
例: 例
class X<T> { public static void PrintTypes() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void), typeof(T), typeof(X<T>), typeof(X<X<T>>), typeof(X<>) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i]); } } } class Test { static void Main() { X<int>.PrintTypes(); } }
では、次の出力が生成されます。
System.Int32 System.Int32 System.String System.Double[] System.Void System.Int32 X`1[System.Int32] X`1[X`1[System.Int32]] X`1[T]
int
とSystem.Int32
は同じ型であることに注意してください。typeof(X<>)
の結果は型引数に依存しませんが、typeof(X<T>)
の結果に依存します。end の例
12.8.19 sizeof 演算子
sizeof
演算子は、特定の型の変数によって占有される 8 ビット バイトの数を返します。 sizeof のオペランドとして指定される型は、 unmanaged_type (§8.8) である必要があります。
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
特定の定義済み型の場合、 sizeof
演算子は、次の表に示すように定数 int
値を生成します。
Expression | 結果 |
---|---|
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
8 |
sizeof(ulong) |
8 |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
8 |
sizeof(bool) |
1 |
sizeof(decimal) |
16 |
列挙型 T
の場合、式 sizeof(T)
の結果は、上記のように、基になる型のサイズと等しい定数値になります。 その他のすべてのオペランド型の場合、 sizeof
演算子は §23.6.9 で指定されます。
12.8.20 オンおよびオフの演算子
checked
演算子とunchecked
演算子は、整数型の算術演算と変換のオーバーフロー チェック コンテキストを制御するために使用されます。
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
checked
演算子は、チェックされたコンテキスト内の包含式を評価し、unchecked
演算子は、チェックされていないコンテキストの包含式を評価します。 checked_expressionまたはunchecked_expressionは、parenthesized_expression (§12.8.5) に正確に対応します。ただし、含まれる式は、指定されたオーバーフロー チェック コンテキストで評価されます。
オーバーフロー チェック コンテキストは、 checked
および unchecked
ステートメント (§13.12) を使用して制御することもできます。
次の操作は、チェックされた演算子とチェックされていない演算子とステートメントによって確立されたオーバーフロー チェック コンテキストの影響を受けます。
- オペランドが整数型または列挙型の場合の定義済みの
++
演算子と--
演算子 (§12.8.16 および §12.9.6)。 - オペランドが整数型の場合、定義済みの
-
単項演算子 (§12.9.3)。 - 両方のオペランドが整数型または列挙型の場合、定義済みの
+
、-
、*
、および/
二項演算子 (§12.10)。 - 整数型または列挙型から別の整数型または列挙型への明示的な数値変換 (§10.3.2)、または
float
またはdouble
から整数型または列挙型への明示的な数値変換。
上記のいずれかの操作で結果が生成され、変換先の型で表すには大きすぎる場合、操作が実行されるコンテキストによって結果の動作が制御されます。
checked
コンテキストで、操作が定数式 (§12.23) の場合、コンパイル時エラーが発生します。 それ以外の場合、実行時に操作が実行されると、System.OverflowException
がスローされます。unchecked
コンテキストでは、変換先の型に収まらない上位ビットを破棄することで結果が切り捨てられます。
checked
またはunchecked
演算子またはステートメントで囲まれていない非定数式 (§12.23) (実行時に評価される式) の場合、外部要因 (コンパイラ スイッチや実行環境構成など) がチェック評価を呼び出さない限り、既定のオーバーフロー チェック コンテキストはオフになります。
定数式 (§12.23) (コンパイル時に完全に評価できる式) の場合、既定のオーバーフロー チェック コンテキストは常にチェックされます。 定数式が明示的に unchecked
コンテキストに配置されていない限り、式のコンパイル時の評価中にオーバーフローが発生すると、常にコンパイル時エラーが発生します。
匿名関数の本体は、匿名関数が発生する checked
または unchecked
コンテキストの影響を受けません。
例: 次のコード内
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() => checked(x * y); // Throws OverflowException static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Depends on default }
どちらの式もコンパイル時に評価できないので、コンパイル時エラーは報告されません。 実行時に、
F
メソッドはSystem.OverflowException
をスローし、G
メソッドは –727379968 (範囲外の結果の下位 32 ビット) を返します。H
メソッドの動作は、コンパイルの既定のオーバーフロー チェック コンテキストによって異なりますが、F
と同じか、G
と同じです。end の例
例: 次のコード内
class Test { const int x = 1000000; const int y = 1000000; static int F() => checked(x * y); // Compile-time error, overflow static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Compile-time error, overflow }
F
で定数式を評価するときに発生するオーバーフローH
、式がchecked
コンテキストで評価されるため、コンパイル時エラーが報告されます。 オーバーフローは、G
で定数式を評価するときにも発生しますが、評価はunchecked
コンテキストで行われるため、オーバーフローは報告されません。end の例
checked
演算子とunchecked
演算子は、"(
" トークンと ")
" トークン内にテキストで含まれる操作のオーバーフロー チェック コンテキストにのみ影響します。 演算子は、含まれている式を評価した結果として呼び出される関数メンバーには影響しません。
例: 次のコード内
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }
F で
checked
を使用してもMultiply
のx * y
の評価には影響しないため、x * y
は既定のオーバーフロー チェック コンテキストで評価されます。end の例
unchecked
演算子は、符号付き整数型の定数を 16 進表記で記述する場合に便利です。
例:
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }
上記の 16 進定数はどちらも
uint
型です。 定数はint
範囲外であるため、unchecked
演算子を使用しない場合、int
にキャストするとコンパイル時エラーが発生します。end の例
注:
checked
演算子とunchecked
演算子とステートメントを使用すると、プログラマはいくつかの数値計算の特定の側面を制御できます。 ただし、一部の数値演算子の動作は、オペランドのデータ型によって異なります。 たとえば、2 つの小数部を乗算すると、明示的にオフになっているコンストラクト内であっても、常にオーバーフロー時に例外が発生します。 同様に、2 つの浮動小数点を乗算しても、明示的にチェックされたコンストラクト内であっても、オーバーフロー時に例外が発生することはありません。 さらに、他の演算子は、既定か明示的かに関係なく、チェック モードの影響を受けません。 end note
12.8.21 既定値の式
既定値の式を使用して、型の既定値 (§9.3) を取得します。
default_value_expression
: explictly_typed_default
| default_literal
;
explictly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
default_literalは既定値 (§9.3) を表します。 型はありませんが、既定のリテラル変換 (§10.2.16) を使用して任意の型に変換できます。
default_value_expressionの結果は、explictly_typed_defaultの明示的な型の既定値 (§9.3)、またはdefault_value_expressionの変換のターゲット型です。
型が次のいずれかである場合、 default_value_expression は定数式 (§12.23) です。
- 参照型
- 参照型であることが知られている型パラメーター (§8.2);
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
;
stackalloc_expressionは、次の 2 つのコンテキストでのみ許可されます。
- local_variable_declarationの初期化式
E
(§13.6.2); - 右オペランド 式、
E
、それ自体がexpression_statementとして発生する単純な代入 (§12.21.2) の式 (§13.7)
どちらのコンテキストでも、 stackalloc_expression は次のようにしか実行できません。
E
全体。- それ自体が
E
全体であるconditional_expressionの 2 番目または 3 番目のオペランド (§12.18)。
unmanaged_type (§8.8) は、新しく割り当てられた場所に格納される項目の種類を示し、式はこれらの項目の数を示します。 これらを組み合わせて、必要な割り当てサイズを指定します。 式の型はint
型に暗黙的に変換できる必要があります。
スタック割り当てのサイズを負にすることはできません。これは、項目の数を負の値に評価される constant_expression として指定するコンパイル時エラーです。
実行時に割り当てられる項目の数が負の値である場合、動作は未定義です。 0 の場合、割り当ては行われず、返される値は実装定義です。 項目を割り当てるのに十分なメモリがない場合は、 System.StackOverflowException
がスローされます。
stackalloc_initializerが存在する場合:
- unmanaged_typeを省略すると、stackalloc_element_initializerのセットに対する最も一般的な型 (§12.6.3.15) の規則に従って推論されます。
- constant_expressionを省略すると、stackalloc_element_initializerの数と推定されます。
- constant_expressionが存在する場合は、stackalloc_element_initializerの数と等しくなります。
各 stackalloc_element_initializer は、 unmanaged_type (§10.2) への暗黙的な変換を持つ必要があります。 stackalloc_element_initializerは、割り当てられたメモリ内の要素を、インデックス 0 から始まる順に初期化します。 stackalloc_initializerがない場合、新しく割り当てられたメモリの内容は未定義です。
stackalloc_expressionの結果は、Span<T>
型のインスタンスです。ここで、T
はunmanaged_typeです。
Span<T>
(§C.3) は ref 構造体型 (§16.2.3) であり、ここでは、型指定された (T
) 項目のインデックス可能なコレクションとして、stackalloc_expressionによって割り当てられたブロックのメモリ ブロックを提示します。- 結果の
Length
プロパティは、割り当てられた項目の数を返します。 - 結果のインデクサー (§15.9) は、割り当てられたブロックの項目に variable_reference (§9.5) を返し、範囲がチェックされます。
注: 安全でないコードで発生する場合、 stackalloc_expression の結果は別の型である可能性があります(§23.9を参照してください。 end note
スタック割り当て初期化子は、 catch
ブロックまたは finally
ブロック (§13.11) では許可されません。
注:
stackalloc
を使用して割り当てられたメモリを明示的に解放する方法はありません。 end note
関数メンバーの実行中に作成されたスタック割り当てメモリ ブロックはすべて、その関数メンバーが戻ったときに自動的に破棄されます。
stackalloc
演算子を除き、C# はガベージ コレクションされていないメモリを管理するための定義済みのコンストラクトを提供しません。 このようなサービスは、通常、サポート クラス ライブラリによって提供されるか、基になるオペレーティング システムから直接インポートされます。
例:
// Memory uninitialized Span<int> span1 = stackalloc int[3]; // Memory initialized Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred Span<int> span3 = stackalloc[] { 11, 12, 13 }; // Error; result is int*, not allowed in a safe context var span4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion from Span<int> to Span<long> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns Span<long> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns Span<long> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // Implicit conversion of Span<T> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // Implicit conversion of Span<T> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; public class Widget<T> { public static implicit operator Widget<T>(Span<double> sp) { return null; } }
span8
の場合、stackalloc
Span<int>
が発生し、暗黙的な演算子によってReadOnlySpan<int>
に変換されます。 同様に、span9
の場合、結果のSpan<double>
は、次に示すように、変換を使用してユーザー定義型Widget<double>
に変換されます。 end の例
12.8.23 nameof 演算子
nameof_expressionは、プログラム エンティティの名前を定数文字列として取得するために使用されます。
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: named_entity_target ('.' identifier type_argument_list?)*
;
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
nameof
はキーワードではないため、単純な名前nameof
の呼び出しによって、nameof_expressionは常に構文的にあいまいになります。 互換性上の理由から、名前nameof
の名前参照 (§12.8.4) が成功した場合、呼び出しが有効かどうかに関係なく、式はinvocation_expressionとして扱われます。 それ以外の場合は nameof_expressionです。
§12.8.4 および §12.8.7 で説明されている規則に従って、コンパイル時にnamed_entityに対して単純な名前とメンバー アクセスの参照が実行。 ただし、 §12.8.4 および §12.8.7 で説明されている参照では インスタンス メンバーが静的コンテキストで見つかったため、エラーが発生しますが、 nameof_expression はそのようなエラーを生成しません。
メソッド グループにtype_argument_listを指定するnamed_entityのコンパイル時エラーです。 型がdynamic
named_entity_targetのコンパイル時エラーです。
nameof_expressionはstring
型の定数式であり、実行時には影響しません。 具体的には、その named_entity は評価されず、明確な代入分析のために無視されます (§9.4.4.22)。 その値は、オプションの最終type_argument_listの前のnamed_entityの最後の識別子であり、次のように変換されます。
- プレフィックス "
@
" が使用されている場合は削除されます。 - 各 unicode_escape_sequence は、対応する Unicode 文字に変換されます。
- すべての formatting_characters が削除されます。
これらは、識別子間の等価性をテストするときに §6.4.3 で適用されるのと同じ変換です。
例:
System.Collections.Generic
名前空間内で宣言List<T>
ジェネリック型を想定した、さまざまなnameof
式の結果を次に示します。using TestAlias = System.String; class Program { static void Main() { var point = (x: 3, y: 4); string n1 = nameof(System); // "System" string n2 = nameof(System.Collections.Generic); // "Generic" string n3 = nameof(point); // "point" string n4 = nameof(point.x); // "x" string n5 = nameof(Program); // "Program" string n6 = nameof(System.Int32); // "Int32" string n7 = nameof(TestAlias); // "TestAlias" string n8 = nameof(List<int>); // "List" string n9 = nameof(Program.InstanceMethod); // "InstanceMethod" string n10 = nameof(Program.GenericMethod); // "GenericMethod" string n11 = nameof(Program.NestedClass); // "NestedClass" // Invalid // string x1 = nameof(List<>); // Empty type argument list // string x2 = nameof(List<T>); // T is not in scope // string x3 = nameof(GenericMethod<>); // Empty type argument list // string x4 = nameof(GenericMethod<T>); // T is not in scope // string x5 = nameof(int); // Keywords not permitted // Type arguments not permitted for method group // string x6 = nameof(GenericMethod<Program>); } void InstanceMethod() { } void GenericMethod<T>() { string n1 = nameof(List<T>); // "List" string n2 = nameof(T); // "T" } class NestedClass { } }
この例の驚くべき部分は、完全な名前空間ではなく "ジェネリック" に
nameof(System.Collections.Generic)
を解決し、"String" ではなく "TestAlias" にnameof(TestAlias)
することです。 end の例
12.8.24 匿名メソッド式
anonymous_method_expressionは、匿名関数を定義する 2 つの方法のうちの 1 つです。 詳細については、 §12.19 で説明します。
12.9 単項演算子
12.9.1 全般
+
、-
、!
(論理否定§12.9.4 のみ)、~
、++
、--
、キャスト、およびawait
演算子は単項演算子と呼ばれます。
注: 後置 null 許容演算子 (§12.8.9)、
!
は、コンパイル時とオーバーロードできないだけの性質のため、上記のリストから除外されます。 end note
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator unary_expression
| '~' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| pointer_indirection_expression // unsafe code support
| addressof_expression // unsafe code support
;
pointer_indirection_expression (§23.6.2) と addressof_expression (§23.6.5) は、安全でないコード (§23) でのみ使用できます。
unary_expressionのオペランドにコンパイル時の型dynamic
がある場合は、動的にバインドされます (§12.3.3)。 この場合、 unary_expression のコンパイル時の型は dynamic
され、以下に示す解決は、オペランドの実行時の型を使用して実行時に行われます。
12.9.2 単項プラス演算子
フォーム +x
の操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項プラス演算子は次のとおりです。
int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);
これらの演算子ごとに、結果はオペランドの値にすぎません。
上で定義したリフトされていない定義済みの単項プラス演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.9.3 単項マイナス演算子
フォーム –x
の操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの単項マイナス演算子は次のとおりです。
整数否定:
int operator –(int x); long operator –(long x);
結果は、0 から
X
を減算することによって計算されます。X
の値がオペランド型の最小表現可能な値 (int
の場合は −2 ²¹、long
の場合は -2⁶ ²) の場合、X
の数学的否定はオペランド型内で表されません。 これがchecked
コンテキスト内で発生した場合、System.OverflowException
がスローされます。unchecked
コンテキスト内で発生した場合、結果はオペランドの値になり、オーバーフローは報告されません。否定演算子のオペランドが
uint
型の場合、long
型に変換され、結果の型がlong
されます。 例外は、int
値−2147483648
(−2 ²¹) を 10 進整数リテラル (§6.4.5.3) として書き込むことが許可される規則です。否定演算子のオペランドが
ulong
型の場合、コンパイル時エラーが発生します。 例外は、long
値−9223372036854775808
(-2⁶²) を 10 進整数リテラル (§6.4.5.3) として書き込むことが許可される規則です。浮動小数点否定:
float operator –(float x); double operator –(double x);
結果は、符号が反転された
X
の値になります。x
がNaN
場合、結果もNaN
。10 進否定:
decimal operator –(decimal x);
結果は、0 から
X
を減算することによって計算されます。 10 進数の否定は、System.Decimal
型の単項マイナス演算子を使用することと同じです。
上で定義したリフトされていない定義済みの単項マイナス演算子のリフト (§12.4.8) フォームも定義済みです。
12.9.4 論理否定演算子
フォーム !x
の操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みの論理否定演算子が 1 つだけ存在します。
bool operator !(bool x);
この演算子は、オペランドの論理否定を計算します。オペランドが true
場合、結果は false
。 オペランドが false
の場合、結果は true
になります。
上記で定義した、リフト解除された定義済みの論理否定演算子のリフト (§12.4.8) フォームも事前に定義されています。
注: 同じ字句トークン (!
) で表されるプレフィックス論理否定演算子と後置 null 許容演算子 (§12.8.9) は異なります。 end note
12.9.5 ビットごとの補数演算子
フォーム ~x
の操作では、単項演算子のオーバーロード解決 (§12.4.4) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 定義済みのビットごとの補数演算子は次のとおりです。
int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);
これらの各演算子の演算の結果は、 x
のビットごとの補数です。
すべての列挙型 E
は、次のビットごとの補数演算子を暗黙的に提供します。
E operator ~(E x);
~x
を評価した結果(X
は基になる型U
を持つ列挙型E
の式)、(E)(~(U)x)
の評価とまったく同じですが、E
への変換は常にunchecked
コンテキストの場合と同じように実行されます (§12.8.20)。
上で定義した未リフトの定義済みビットごとの補数演算子のリフト (§12.4.8) フォームも定義済みです。
12.9.6 プレフィックスインクリメント演算子とデクリメント演算子
pre_increment_expression
: '++' unary_expression
;
pre_decrement_expression
: '--' unary_expression
;
プレフィックスインクリメントまたはデクリメント演算のオペランドは、変数、プロパティ アクセス、またはインデクサー アクセスとして分類される式でなければなりません。 演算の結果は、オペランドと同じ型の値になります。
プレフィックスインクリメントまたはデクリメント演算のオペランドがプロパティまたはインデクサーアクセスである場合、プロパティまたはインデクサーは、get アクセサーと set アクセサーの両方を持つ必要があります。 そうでない場合は、バインド時エラーが発生します。
単項演算子のオーバーロード解決 (§12.4.4) を適用して、特定の演算子の実装を選択します。 定義済みの ++
演算子と --
演算子は、 sbyte
、 byte
、 short
、 ushort
、 int
、 uint
、 long
、 ulong
、 char
、 float
、 double
、 decimal
、および任意の列挙型に対して存在します。 定義済みの ++
演算子は、オペランドに 1
を追加することによって生成された値を返し、定義済みの --
演算子は、オペランドから 1
を減算することによって生成された値を返します。 checked
コンテキストでは、この加算または減算の結果が結果型の範囲外で、結果の型が整数型または列挙型の場合、System.OverflowException
がスローされます。
選択した単項演算子の戻り値の型から unary_expressionの型への暗黙的な変換が必要です。それ以外の場合は、コンパイル時エラーが発生します。
フォーム ++x
または --x
のプレフィックスインクリメントまたはデクリメント操作の実行時処理は、次の手順で構成されます。
x
が変数として分類される場合:x
は、変数を生成するために評価されます。x
の値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。- 演算子によって返される値は、
x
の型に変換されます。 結果の値は、x
の評価によって指定された場所に格納されます。 - が操作の結果になります。
x
がプロパティまたはインデクサー アクセスとして分類されている場合:- インスタンス式 (
x
がstatic
されていない場合) と、x
に関連付けられている引数リスト (x
がインデクサー アクセスの場合) が評価され、その結果は後続の get アクセサー呼び出しおよび set アクセサー呼び出しで使用されます。 x
の get アクセサーが呼び出されます。- get アクセサーによって返される値は、選択した演算子のオペランド型に変換され、この値を引数として使用して演算子が呼び出されます。
- 演算子によって返される値は、
x
の型に変換されます。x
の set アクセサーは、値引数としてこの値を使用して呼び出されます。 - この値は、操作の結果にもなります。
- インスタンス式 (
++
演算子と--
演算子では、後置表記もサポートされます (§12.8.16)。 x++
またはx--
の結果は、操作の前のx
の値ですが、++x
または--x
の結果は、操作後のx
の値です。 どちらの場合も、 x
自体は操作後に同じ値を持ちます。
演算子 ++
または演算子 --
実装は、後置表記またはプレフィックス表記を使用して呼び出すことができます。 2 つの表記に対して個別の演算子を実装することはできません。
前に定義した、リフト解除された定義済みのプレフィックスインクリメント演算子とデクリメント演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.9.7 キャスト式
cast_expressionは、式を明示的に特定の型に変換するために使用されます。
cast_expression
: '(' type ')' unary_expression
;
フォーム (T)E
のcast_expression(T
は型、E
はunary_expression)、E
の値の明示的な変換 (§10.3) を実行T
。 E
からT
への明示的な変換が存在しない場合は、バインド時エラーが発生します。 それ以外の場合、明示的な変換によって生成される値が結果になります。 E
が変数を表している場合でも、結果は常に値として分類されます。
cast_expressionの文法は、特定の構文のあいまいさにつながります。
例: 式
(x)–y
は、cast_expression (x
型への–y
のキャスト) またはparenthesized_expression (値x – y
を計算する) と組み合わせたadditive_expressionとして解釈できます。 end の例
cast_expressionあいまいさを解決するために、次の規則が存在します。かっこで囲まれた 1 つ以上のトークン (§6.4) のシーケンスは、次の少なくとも 1 つが該当する場合にのみ、cast_expressionの開始と見なされます。
- トークンのシーケンスは型に対して正しい文法ですが、式には適していません。
- トークンのシーケンスは型の正しい文法であり、終わりかっこの直後のトークンはトークン "
~
" です。 トークン "!
"、トークン "(
"、識別子 (§6.4.3)、リテラル (§6.4.5)、または任意のキーワード (§6.4.4) (as
およびis
を除く)。
上記の "正しい文法" という用語は、トークンのシーケンスが特定の文法の生成に準拠する必要があることを意味します。 具体的には、構成識別子の実際の意味は考慮されません。
例:
x
とy
が識別子の場合、x.y
が実際に型を表していない場合でも、x.y
は型の正しい文法になります。 end の例
注:
x
とy
が識別子、(x)y
、(x)(y)
、および(x)(-y)
がcast_expressionである場合、x
が型を識別する場合でも、(x)-y
は無効になります。 ただし、x
が定義済みの型 (int
など) を識別するキーワードである場合、4 つのフォームはすべて cast_expressionされます (このようなキーワードが単独で式にできない可能性があるため)。 end note
12.9.8 Await 式
12.9.8.1 全般
await
演算子は、オペランドによって表される非同期操作が完了するまで、外側の非同期関数の評価を中断するために使用されます。
await_expression
: 'await' unary_expression
;
await_expressionは、非同期関数 (§15.15) の本体でのみ許可されます。 最も近い外側の非同期関数内では、 await_expression は次の場所では発生しません。
- 入れ子になった (非同期ではない) 匿名関数内
- lock_statementのブロック内
- 匿名関数から式ツリー型への変換 (§10.7.3)
- 安全でないコンテキストの場合
注: query_expression内のほとんどの場所でawait_expressionは発生できません。非同期でないラムダ式を使用するように構文的に変換されるためです。 end note
非同期関数内では、 await
は available_identifier として使用されませんが、逐語的な識別子 @await
を使用できます。 したがって、 await_expressionと識別子を含むさまざまな式の間に構文的なあいまいさはありません。 非同期関数の外部では、 await
は通常の識別子として機能します。
await_expressionのオペランドは、task と呼ばれます。 これは、 await_expression が評価されるときに完了する場合と完了しない場合がある非同期操作を表します。 await
演算子の目的は、待機中のタスクが完了するまで外側の非同期関数の実行を中断し、その結果を取得することです。
12.9.8.2 Awaitable 式
await_expressionのタスクは、可能である必要があります。 次のいずれかが保持されている場合、式 t
は待機可能です。
t
はコンパイル時の型ですdynamic
t
には、パラメーターがなく、型パラメーターを持たないGetAwaiter
と呼ばれる、アクセス可能なインスタンスまたは拡張メソッドがあり、次のすべてが保持される戻り値の型A
があります。A
は、インターフェイスSystem.Runtime.CompilerServices.INotifyCompletion
を実装します (以下、簡潔にするためにINotifyCompletion
と呼ばれます)A
には、アクセス可能で読み取り可能なインスタンス プロパティIsCompleted
型がありますbool
A
には、パラメーターと型パラメーターを持たないアクセス可能なインスタンス メソッドGetResult
があります
GetAwaiter
メソッドの目的は、タスクの awaiter を取得することです。 A
型は、await 式の awaiter 型と呼ばれます。
IsCompleted
プロパティの目的は、タスクが既に完了しているかどうかを判断することです。 その場合、評価を中断する必要はありません。
INotifyCompletion.OnCompleted
メソッドの目的は、タスクに "継続" をサインアップすることです。つまり、タスクが完了すると呼び出されるデリゲート (System.Action
型)。
GetResult
メソッドの目的は、完了したタスクの結果を取得することです。 この結果は、正常に完了した場合や、結果値がある場合や、 GetResult
メソッドによってスローされる例外である可能性があります。
12.9.8.3 await 式の分類
式 await t
は、式 (t).GetAwaiter().GetResult()
と同じように分類されます。 したがって、 GetResult
の戻り値の型が void
場合、 await_expression は何も分類されません。 void
以外の戻り値の型T
がある場合、await_expressionはT
型の値として分類されます。
12.9.8.4 await 式の実行時評価
実行時に、式 await t
は次のように評価されます。
- awaiter
a
は、式(t).GetAwaiter()
を評価することによって取得されます。 bool
b
は、式(a).IsCompleted
を評価することによって取得されます。b
がfalse
場合、評価は、インターフェイスSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
を実装a
かどうかによって異なります (ここでは簡潔にするためにICriticalNotifyCompletion
と呼ばれます)。 このチェックはバインド時に行われます。つまり、a
がコンパイル時の型dynamic
を持つ場合は実行時、それ以外の場合はコンパイル時です。 再開デリゲートr
示します (§15.15)。a
ICriticalNotifyCompletion
を実装していない場合は、式((a) as INotifyCompletion).OnCompleted(r)
が評価されます。a
がICriticalNotifyCompletion
を実装している場合、式((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
が評価されます。- 評価が中断され、非同期関数の現在の呼び出し元に制御が返されます。
- 直後 (
b
がtrue
された場合) または再開デリゲートの後の呼び出し時 (b
がfalse
された場合)、式(a).GetResult()
が評価されます。 値が返された場合、その値は await_expressionの結果になります。 それ以外の場合、結果は何もありません。
INotifyCompletion.OnCompleted
およびICriticalNotifyCompletion.UnsafeOnCompleted
インターフェイス メソッドの awaiter の実装により、デリゲートr
が最大 1 回呼び出されます。 それ以外の場合、外側の非同期関数の動作は未定義です。
12.10 算術演算子
12.10.1 全般
*
、/
、%
、+
、および-
演算子は算術演算子と呼ばれます。
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression
| multiplicative_expression '/' unary_expression
| multiplicative_expression '%' unary_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
算術演算子のオペランドにコンパイル時の型 dynamic
がある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamic
され、コンパイル時型が dynamic
されているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。
12.10.2 乗算演算子
フォーム x * y
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの乗算演算子を次に示します。 演算子はすべて、 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 のすべての可能な組み合わせの結果を示します。 この表では、
x
とy
は正の有限値です。z
はx * y
の結果であり、最も近い表現可能な値に丸められます。 結果の大きさが変換先の型に対して大きすぎる場合、z
は無限大です。 丸めのために、x
もy
もゼロでなくても、z
は 0 になる可能性があります。+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+0
-0
+∞
-∞
NaN
-x
-z
+z
-0
+0
-∞
+∞
NaN
+0
+0
-0
+0
-0
NaN
NaN
NaN
-0
-0
+0
-0
+0
NaN
NaN
NaN
+∞
+∞
-∞
NaN
NaN
+∞
-∞
NaN
-∞
-∞
+∞
NaN
NaN
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(特に明記されていない限り、 §12.10.2–§12.10.6 の浮動小数点テーブルでは "
+
" を使用すると、値が正であることを意味します。"-
" を使用すると、値が負であることを意味し、符号の欠如は正または負の符号 (NaN) であることを意味します)。10 進乗算:
decimal operator *(decimal x, decimal y);
結果の値の大きさが 10 進形式で表すには大きすぎる場合は、
System.OverflowException
がスローされます。 丸めのため、どちらのオペランドも 0 でない場合でも、結果は 0 になる可能性があります。 丸める前の結果のスケールは、2 つのオペランドのスケールの合計です。 10 進数乗算は、System.Decimal
型の乗算演算子を使用することと同じです。
上記で定義した、リフトされていない定義済みの乗算演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.10.3 除算演算子
フォーム x / y
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの除算演算子を次に示します。 演算子はすべて、 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);
右オペランドの値が 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 のすべての可能な組み合わせの結果を示します。 この表では、
x
とy
は正の有限値です。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 になる可能性があります。 丸める前の結果のスケールは、正確な結果と同じ結果を保持する優先スケールに最も近いスケールです。 推奨されるスケールは、y
のスケールx
小さいスケールです。10 進数除算は、
System.Decimal
型の除算演算子を使用することと同じです。
上記で定義したリフトされていない定義済み除算演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.10.4 剰余演算子
フォーム x % y
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの剰余演算子を次に示します。 演算子はすべて、 x
と y
の間の除算の剰余を計算します。
整数の剰余:
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);
x % y
の結果は、x – (x / y) * y
によって生成される値です。y
が 0 の場合、System.DivideByZeroException
がスローされます。左オペランドが最小の
int
またはlong
値で、右オペランドが–1
場合、x / y
が例外をスローする場合にのみ、System.OverflowException
がスローされます。浮動小数点剰余:
float operator %(float x, float y); double operator %(double x, double y);
次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、
x
とy
は正の有限値です。z
はx % y
の結果であり、x – n * y
として計算されます。ここで、n はx / y
以下の最大の整数です。 剰余を計算するこの方法は整数オペランドに使用される方法と似ていますが、IEC 60559 定義 (n
がx / y
に最も近い整数) とは異なります。+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
+z
NaN
NaN
+x
+x
NaN
-x
-z
-z
NaN
NaN
-x
-x
NaN
+0
+0
+0
NaN
NaN
+0
+0
NaN
-0
-0
-0
NaN
NaN
-0
-0
NaN
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
10 進剰
decimal operator %(decimal x, decimal y);
右オペランドの値が 0 の場合、
System.DivideByZeroException
がスローされます。 これは、System.ArithmeticException
(またはそのサブクラス) がスローされたときに実装定義されます。 準拠する実装は、x / y
が例外をスローしない場合でも、x % y
の例外をスローしません。 丸める前の結果の小数点以下桁数は、2 つのオペランドのスケールの大きい方であり、0 以外の場合は結果の符号はx
のスケールと同じです。10 進剰余は、
System.Decimal
型の剰余演算子を使用することと同じです。注: これらの規則により、すべての型に対して、結果が左オペランドの反対の符号を持つことはありません。 end note
上記で定義した、リフト解除された定義済みの剰余演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.10.5 加算演算子
フォーム x + y
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの加算演算子を次に示します。 数値型と列挙型の場合、定義済みの加算演算子は、2 つのオペランドの合計を計算します。 一方または両方のオペランドが string
型の場合、定義済みの加算演算子はオペランドの文字列表現を連結します。
整数の加算:
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y
checked
コンテキストでは、合計が結果の型の範囲外の場合、System.OverflowException
がスローされます。unchecked
コンテキストでは、オーバーフローは報告されず、結果の種類の範囲外の重要な上位ビットは破棄されます。浮動小数点加算:
float operator +(float x, float y); double operator +(double x, double y);
合計は IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、
x
とy
は 0 以外の有限値であり、z
はx + y
の結果です。x
とy
が同じ大きさで符号が逆の場合、z
は正のゼロになります。x + y
が大きすぎて変換先の型で表すには、z
はx + 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
は列挙型、U
はE
の基になる型です。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
メソッドを呼び出すことによって、その文字列形式に変換されます。ToString
がnull
を返す場合は、空の文字列が置換されます。例:
class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // Displays s = >< int i = 1; Console.WriteLine("i = " + i); // Displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // Displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // Displays d = 2.900 } }
コメントに表示される出力は、米国英語システムの一般的な結果です。 正確な出力は、実行環境のリージョン設定によって異なります。 文字列連結演算子自体はいずれの場合も同じように動作しますが、実行中に暗黙的に呼び出される
ToString
メソッドは、リージョン設定の影響を受ける可能性があります。end の例
文字列連結演算子の結果は、左オペランドの文字とそれに続く右オペランドの文字で構成される
string
です。 文字列連結演算子は、null
値を返しません。 結果の文字列を割り当てるのに十分なメモリがない場合は、System.OutOfMemoryException
がスローされる可能性があります。デリゲートの組み合わせ。 すべてのデリゲート型は、次の定義済みの演算子を暗黙的に提供します。ここで、
D
はデリゲート型です。D operator +(D x, D y);
最初のオペランドが
null
場合、演算の結果は 2 番目のオペランドの値になります (null
場合でも)。 それ以外の場合、2 番目のオペランドがnull
場合、演算の結果は最初のオペランドの値になります。 それ以外の場合、操作の結果は、呼び出しリストが最初のオペランドの呼び出しリスト内の要素で構成され、その後に 2 番目のオペランドの呼び出しリスト内の要素が続く新しいデリゲート インスタンスになります。 つまり、結果のデリゲートの呼び出しリストは、2 つのオペランドの呼び出しリストを連結したものです。注: デリゲートの組み合わせの例については、 §12.10.6 および §20.6 を参照してください。
System.Delegate
はデリゲート型ではないため、演算子 + は定義されていません。end note
上で定義したリフト解除された定義済み加算演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.10.6 減算演算子
フォーム x – y
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの減算演算子を次に示します。 演算子はすべて、x
からy
を減算します。
整数減算:
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong y
checked
コンテキストでは、差が結果の型の範囲外の場合、System.OverflowException
がスローされます。unchecked
コンテキストでは、オーバーフローは報告されず、結果の種類の範囲外の重要な上位ビットは破棄されます。浮動小数点減算:
float operator –(float x, float y); double operator –(double x, double y);
違いは IEC 60559 算術の規則に従って計算されます。 次の表に、0 以外の有限値、ゼロ、無限大、および NaN のすべての可能な組み合わせの結果を示します。 この表では、
x
とy
は 0 以外の有限値であり、z
はx – y
の結果です。x
とy
が等しい場合、z
は正のゼロになります。x – y
が大きすぎて変換先の型で表すには、z
はx – 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
は列挙型、U
はE
の基になる型です。U operator –(E x, E y);
この演算子は、
(U)((U)x – (U)y)
とまったく同じように評価されます。 つまり、演算子は、x
とy
の序数値の差を計算し、結果の型は列挙型の基になる型になります。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 番目のオペランドのリストが、最初のオペランドのリスト内の連続するエントリの複数のサブリストと一致する場合、連続するエントリの最後に一致するサブリストが削除されます。
- それ以外の場合、演算の結果は左オペランドの値になります。
- デリゲート等値演算子 (§12.12.9) によって決定されたリストが等しい場合、操作の結果は
オペランドのリスト (存在する場合) のどちらもプロセスで変更されません。
例:
delegate void D(int x); class C { public static void M1(int i) { ... } public static void M2(int i) { ... } } class Test { static void Main() { D cd1 = new D(C.M1); D cd2 = new D(C.M2); D list = null; list = null - cd1; // null list = (cd1 + cd2 + cd2 + cd1) - null; // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - cd1; // M1 + M2 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2); // M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2); // M1 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1); // M1 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1); // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1); // null } }
end の例
- 最初のオペランドが
上で定義したリフト解除された定義済みの減算演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.11 シフト演算子
<<
演算子と>>
演算子は、ビットシフト操作を実行するために使用されます。
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
shift_expressionのオペランドにコンパイル時の型dynamic
がある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamic
され、コンパイル時型が dynamic
されているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。
フォーム x << count
または x >> count
の操作では、2 項演算子のオーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
オーバーロードされたシフト演算子を宣言する場合、最初のオペランドの型は常に演算子宣言を含むクラスまたは構造体になり、2 番目のオペランドの型は常に int
。
定義済みのシフト演算子を次に示します。
左シフト:
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);
<<
演算子x
、以下で説明するように計算されたビット数だけ左にシフトします。結果の種類の
x
の範囲外の上位ビットは破棄され、残りのビットは左にシフトされ、下位の空のビット位置は 0 に設定されます。右シフト:
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);
>>
演算子は、次に示すように計算されたビット数だけ右x
シフトします。x
がint
型またはlong
型の場合、x
の下位ビットは破棄され、残りのビットは右にシフトされ、x
が負でない場合は空の上位ビット位置が 0 に設定され、x
が負の場合は 1 に設定されます。x
がuint
型またはulong
型の場合、x
の下位ビットは破棄され、残りのビットは右にシフトされ、上位の空のビット位置は 0 に設定されます。
定義済みの演算子の場合、シフトするビット数は次のように計算されます。
x
の型がint
またはuint
の場合、シフト数は下位 5 ビットのcount
によって与えられます。 つまり、シフト数はcount & 0x1F
から計算されます。x
の型がlong
またはulong
の場合、シフトカウントはcount
の下位 6 ビットによって与えられます。 つまり、シフト数はcount & 0x3F
から計算されます。
結果のシフト数が 0 の場合、シフト演算子は単に x
の値を返します。
シフト演算が原因でオーバーフローが発生することはなく、checked と unchecked のコンテキストで同じ結果が生成されることはありません。
>>
演算子の左オペランドが符号付き整数型の場合、演算子は順序指定シフトを実行し、オペランドの最上位ビット (符号ビット) の値が上位の空のビット位置に伝達されます。 >>
演算子の左オペランドが符号なし整数型の場合、演算子は論理的なシフト右シフトを実行します。ここで、高次の空のビット位置は常に 0 に設定されます。 オペランド型から推論された逆の演算を実行するには、明示的なキャストを使用できます。
例:
x
がint
型の変数である場合、操作unchecked ((int)((uint)x >> y))
x
の論理シフト権限を実行します。 end の例
上記で定義したリフトされていない定義済みシフト演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.12 関係演算子と型テスト演算子
12.12.1 全般
==
、!=
、<
、>
、<=
、>=
、is
、およびas
演算子は、リレーショナル演算子と型テスト演算子と呼ばれます。
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression '<=' shift_expression
| relational_expression '>=' shift_expression
| relational_expression 'is' type
| relational_expression 'is' pattern
| relational_expression 'as' type
;
equality_expression
: relational_expression
| equality_expression '==' relational_expression
| equality_expression '!=' relational_expression
;
注:
is
演算子の右オペランドを検索するには、最初に 型、次に複数のトークンにまたがる可能性がある 式 としてテストする必要があります。 オペランドが expreesion である場合パターン式は、少なくとも shift_expressionほど高い優先順位を持つ必要があります。 end note
is
演算子については §12.12.12 で説明し、as
演算子については §12.12.13 で説明します。
==
、!=
、<
、>
、<=
、>=
の演算子は、comparison 演算子です。
<
、>
、<=
、または>=
演算子のオペランドとしてdefault_literal (§12.8.21) を使用すると、コンパイル時エラーが発生します。
==
演算子または!=
演算子の両方のオペランドとしてdefault_literalを使用すると、コンパイル時エラーが発生します。 is
またはas
演算子の左オペランドとしてdefault_literalを使用すると、コンパイル時エラーが発生します。
比較演算子のオペランドにコンパイル時の型 dynamic
がある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamic
され、コンパイル時の型が dynamic
されているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。
フォーム x «op» y
の演算 («op» は比較演算子) の場合、オーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。 equality_expressionの両方のオペランドがnull
リテラルの場合、オーバーロードの解決は実行されず、演算子が==
か!=
かに応じて、式はtrue
またはfalse
の定数値に評価されます。
定義済みの比較演算子については、次のサブクラスで説明します。 次の表に示すように、定義済みのすべての比較演算子はブール型の結果を返します。
操作 | 結果 |
---|---|
x == y |
true x がy と等しい場合はfalse 。それ以外の場合は |
x != y |
true x がy と等しくない場合はfalse それ以外の場合 |
x < y |
x が y より小さい場合は true 、それ以外の場合は false |
x > y |
x が y より大きい場合は true 、それ以外の場合は false |
x <= y |
x が y 以下の場合は true 、それ以外の場合は false |
x >= y |
x が y 以上の場合は 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 つの整数オペランドの数値を比較し、特定の関係がtrue
かfalse
かを示すbool
値を返します。
上記で定義したリフトされていない定義済みの整数比較演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.12.3 浮動小数点比較演算子
定義済みの浮動小数点比較演算子は次のとおりです。
bool operator ==(float x, float y);
bool operator ==(double x, double y);
bool operator !=(float x, float y);
bool operator !=(double x, double y);
bool operator <(float x, float y);
bool operator <(double x, double y);
bool operator >(float x, float y);
bool operator >(double x, double y);
bool operator <=(float x, float y);
bool operator <=(double x, double y);
bool operator >=(float x, float y);
bool operator >=(double x, double y);
演算子は、IEC 60559 標準の規則に従ってオペランドを比較します。
いずれかのオペランドが NaN の場合、結果がtrue
!=
を除くすべての演算子に対して結果がfalse
されます。 2 つのオペランドの場合、 x != y
は常に !(x == y)
と同じ結果を生成します。 ただし、一方または両方のオペランドが NaN の場合、<
、>
、<=
、および>=
演算子は逆演算子の論理否定と同じ結果を生成しません。
例:
x
とy
のいずれかが NaN の場合、x < y
はfalse
されますが、!(x >= y)
はtrue
。 end の例
どちらのオペランドも NaN でない場合、演算子は 2 つの浮動小数点オペランドの値を順序付けと比較します
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
ここで、 min
と max
は、指定された浮動小数点形式で表すことができる最小および最大の正の有限値です。 この順序の主な影響は次のとおりです。
- 負のゼロと正のゼロは等しいと見なされる。
- 負の無限大は、他のすべての値より小さいと見なされますが、別の負の無限大と等しくなります。
- 正の無限大は、他のすべての値より大きいと見なされますが、別の正の無限大と等しくなります。
上記で定義した、リフトされていない定義済みの浮動小数点比較演算子のリフト (§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 進オペランドの数値を比較し、特定の関係がtrue
かfalse
かを示すbool
値を返します。 各 10 進比較は、 System.Decimal
型の対応するリレーショナル演算子または等値演算子を使用することと同じです。
上記で定義したリフトされていない定義済みの 10 進数比較演算子のリフト (§12.4.8) フォームも定義済みです。
12.12.5 ブール等値演算子
定義済みのブール等値演算子は次のとおりです。
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
==
の結果は、x
とy
の両方がtrue
されている場合、またはx
とy
の両方がfalse
されている場合にtrue
されます。 それ以外の場合、結果は false
です。
!=
の結果は、x
とy
の両方がtrue
されている場合、またはx
とy
の両方がfalse
されている場合にfalse
されます。 それ以外の場合、結果は true
です。 オペランドが bool
型の場合、 !=
演算子は ^
演算子と同じ結果を生成します。
上記で定義したリフトされていない定義済みのブール等値演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.12.6 列挙比較演算子
すべての列挙型は、次の定義済みの比較演算子を暗黙的に提供します。
bool operator ==(E x, E y);
bool operator !=(E x, E y);
bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);
x «op» y
を評価した結果、x と y は基になる型U
を持つ列挙型E
の式であり、«op» は比較演算子の 1 つであり、((U)x) «op» ((U)y)
の評価とまったく同じです。 つまり、列挙型の比較演算子は、単に 2 つのオペランドの基になる整数値を比較します。
上記で定義したリフトされていない定義済み列挙比較演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.12.7 参照型の等値演算子
すべてのクラス型 C
は、次の定義済みの参照型の等値演算子を暗黙的に提供します。
bool operator ==(C x, C y);
bool operator !=(C x, C y);
定義済みの等値演算子が C
に存在しない限り (たとえば、 C
が string
または System.Delegate
の場合)。
演算子は、等しいか等しくないかの 2 つの参照を比較した結果を返します。 operator ==
は、x
とy
が同じインスタンスを参照している場合、または両方がnull
場合にのみtrue
を返しますが、operator !=
は同じオペランドを持つoperator ==
がfalse
を返す場合にのみtrue
を返します。
標準の適用規則 (§12.6.4.2) に加えて、定義済みの参照型の等値演算子を適用するには、次のいずれかが必要です。
- どちらのオペランドも、 reference_type またはリテラル
null
であることが知られている型の値です。 さらに、どちらかのオペランドから他方のオペランドの型への ID または明示的な参照変換 (§10.3.5) が存在します。 - 1 つのオペランドはリテラル
null
で、もう 1 つのオペランドはT
型の値です。ここで、T
は値型とは認識されず、値型制約を持たない type_parameter です。- 実行時に
T
が null 非許容値型の場合、==
の結果はfalse
され、!=
の結果はtrue
。 - 実行時
T
が null 許容値型の場合、結果はオペランドのHasValue
プロパティから計算されます (§12.12.10 で説明されています)。 - 実行時に
T
が参照型の場合、オペランドがnull
の場合は結果がtrue
され、それ以外の場合はfalse
。
- 実行時に
これらの条件のいずれかが当てはまる場合を除き、バインド時エラーが発生します。
注: これらのルールの主な影響は次のとおりです。
- 定義済みの参照型の等値演算子を使用して、バインド時に異なることがわかっている 2 つの参照を比較するのは、バインド時エラーです。 たとえば、オペランドのバインディング時の型が 2 つのクラス型であり、どちらも他方から派生していない場合、2 つのオペランドが同じオブジェクトを参照することはできません。 したがって、操作はバインド時エラーと見なされます。
- 定義済みの参照型の等値演算子では、値型オペランドの比較は許可されません (型パラメーターが特別に処理される
null
と比較される場合を除く)。- 定義済みの参照型の等値演算子のオペランドはボックス化されません。 新しく割り当てられたボックス化されたインスタンスへの参照は、必ずしも他のすべての参照と異なるため、このようなボックス化操作を実行することは意味がありません。
フォーム
x == y
またはx != y
の操作の場合、該当するユーザー定義のoperator ==
またはoperator !=
が存在する場合、演算子オーバーロード解決規則 (§12.4.5) は、定義済みの参照型の等値演算子の代わりにその演算子を選択します。 一方または両方のオペランドを明示的にキャストして、定義済みの参照型の等値演算子を常に選択object
。end note
例: 次の例では、制約のない型パラメーター型の引数が
null
されているかどうかを確認します。class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }
x == null
コンストラクトは、T
が null 非許容値型を表す可能性があり、T
が null 非許容値型の場合にfalse
するように単純に定義されている場合でも許可されます。end の例
フォーム x == y
または x != y
の操作で、該当する operator ==
または operator !=
が存在する場合、演算子オーバーロードの解決 (§12.4.5) ルールは、定義済みの参照型の等値演算子ではなく、その演算子を選択します。
注: 両方のオペランドを明示的に
object
型にキャストすることで、定義済みの参照型の等値演算子を常に選択できます。 end note
例: 例
class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }
この例では、次のように出力されます。
True False False False
s
変数とt
変数は、同じ文字を含む 2 つの異なる文字列インスタンスを参照します。 最初の比較では、定義済みの文字列等値演算子 (§12.12.8) が両方のオペランドがstring
型である場合に選択されるため、True
出力されます。 残りの比較では、string
型のoperator ==
のオーバーロードは、いずれかのオペランドにバインド時型のobject
がある場合は適用できないため、すべての出力False
されます。上記の手法は、値型では意味を持たないことに注意してください。 例を示します。
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }
は
False
を出力します。これは、キャストによってボックス化されたint
値の 2 つの個別のインスタンスへの参照が作成されるためです。end の例
12.12.8 文字列等値演算子
定義済みの文字列等値演算子は次のとおりです。
bool operator ==(string x, string y);
bool operator !=(string x, string y);
次のいずれかが当てはまる場合、2 つの string
値が等しいと見なされます。
- どちらの値も
null
。 - どちらの値も、各文字位置に同じ長さと同じ文字を持つ文字列インスタンスへの
null
以外の参照です。
文字列の等値演算子は、文字列参照ではなく文字列値を比較します。 2 つの個別の文字列インスタンスにまったく同じ文字シーケンスが含まれている場合、文字列の値は等しくなりますが、参照は異なります。
注: §12.12.7 で説明されているように参照型の等値演算子を使用して、文字列値の代わりに文字列参照を比較できます。 end note
12.12.9 等値演算子を委任する
定義済みのデリゲート等値演算子は次のとおりです。
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
2 つのデリゲート インスタンスは、次のように等しいと見なされます。
- いずれかのデリゲート インスタンスが
null
されている場合、両方がnull
されている場合にのみ等しくなります。 - デリゲートの実行時の種類が異なる場合、それらは等しいことはありません。
- 両方のデリゲート インスタンスに呼び出しリスト (§20.2) がある場合、それらのインスタンスは、呼び出しリストの長さが同じで、1 つの呼び出しリスト内の各エントリが、対応するエントリと (以下で定義されているように) 等しい場合にのみ、他の呼び出しリストの順序で等しくなります。
次の規則は、呼び出しリスト エントリの等価性を制御します。
- 2 つの呼び出しリスト 項目が両方とも同じ静的メソッドを参照している場合、項目は等しくなります。
- 2 つの呼び出しリスト エントリが両方とも同じターゲット オブジェクトで同じ非静的メソッドを参照している場合 (参照等値演算子で定義されている場合)、エントリは等しくなります。
- キャプチャされた外部変数インスタンスの同じ (空の可能性がある) セットを持つ、セマンティックに同じ匿名関数 (§12.19) の評価から生成される呼び出しリスト エントリは、等しい (ただし必須ではありません) 許可されます。
演算子のオーバーロード解決がいずれかのデリゲート等値演算子に解決され、両方のオペランドのバインド時型が、System.Delegate
ではなく §20 で説明されているデリゲート型であり、バインド型のオペランド型間に ID 変換がない場合、バインド時エラーが発生します。
注: この規則では、異なる種類のデリゲートのインスタンスへの参照であるため、
null
以外の値を等しいと見なすことができる比較を禁止します。 end note
12.12.10 null 許容値型と null リテラル間の等値演算子
==
演算子と!=
演算子を使用すると、1 つのオペランドを null 許容値型の値にし、もう一方のオペランドをnull
リテラルにすることができます(操作に対して定義済みまたはユーザー定義の演算子が存在しない場合でも)。
いずれかのフォームの操作の場合
x == null null == x x != null null != x
ここで、x
は null 許容値型の式です。演算子オーバーロードの解決 (§12.4.5) が該当する演算子を見つけられなかった場合、結果は代わりに x
の HasValue
プロパティから計算されます。 具体的には、最初の 2 つのフォームは !x.HasValue
に変換され、最後の 2 つのフォームは x.HasValue
に変換されます。
12.12.11 タプル等値演算子
タプル等値演算子は、タプル オペランドの要素に構文順にペアワイズで適用されます。
==
演算子または!=
演算子の各オペランドx
およびy
がタプルとして、またはタプル型 (§8.3.11) の値として分類される場合、演算子は tuple 等値演算子です。
オペランド e
がタプルとして分類される場合、 e1...en
要素はタプル式の要素式を評価した結果になります。 それ以外の場合、e
がタプル型の値である場合、要素はt.Item1...t.Itemn
t
e
評価の結果になります。
タプル等値演算子のオペランド x
と y
は同じアリティを持っているか、コンパイル時エラーが発生します。 xi
とyi
の各要素のペアに対して、同じ等値演算子が適用され、型bool
、dynamic
、bool
への暗黙的な変換を持つ型、またはtrue
演算子とfalse
演算子を定義する型の結果が得られます。
タプル等値演算子 x == y
は、次のように評価されます。
- 左側のオペランド
x
が評価されます。 - 右側のオペランド
y
が評価されます。 - 構文の順序で
xi
およびyi
される要素のペアごとに、次の手順を実行します。- 演算子
xi == yi
が評価され、bool
型の結果が次のように取得されます。- 比較によって
bool
が生成された場合、それが結果になります。 - それ以外の場合、比較によって
dynamic
が生成された場合、演算子false
が動的に呼び出され、結果のbool
値は論理否定演算子 (!
) で否定されます。 - それ以外の場合、比較の型に
bool
への暗黙的な変換がある場合、その変換が適用されます。 - それ以外の場合、比較の型に演算子
false
がある場合、その演算子が呼び出され、結果のbool
値は論理否定演算子 (!
) で否定されます。
- 比較によって
- 結果の
bool
がfalse
場合、それ以上の評価は行われず、タプル等値演算子の結果はfalse
。
- 演算子
- すべての要素比較が
true
生成された場合、タプル等値演算子の結果はtrue
。
タプル等値演算子 x != y
は、次のように評価されます。
- 左側のオペランド
x
が評価されます。 - 右側のオペランド
y
が評価されます。 - 構文の順序で
xi
およびyi
される要素のペアごとに、次の手順を実行します。- 演算子
xi != yi
が評価され、bool
型の結果が次のように取得されます。- 比較によって
bool
が生成された場合、それが結果になります。 - それ以外の場合、比較によって
dynamic
が生成された場合、演算子true
が動的に呼び出され、結果のbool
値が結果になります。 - それ以外の場合、比較の型に
bool
への暗黙的な変換がある場合、その変換が適用されます。 - それ以外の場合、比較の型に演算子
true
がある場合、その演算子が呼び出され、結果のbool
値が結果になります。
- 比較によって
- 結果の
bool
がtrue
場合、それ以上の評価は行われず、タプル等値演算子の結果はtrue
。
- 演算子
- すべての要素比較が
false
生成された場合、タプル等値演算子の結果はfalse
。
12.12.12 Is 演算子
is
演算子には 2 つの形式があります。 1 つは、右側に型を持つ is 型演算子です。 もう 1 つは、右側にパターンがある is-pattern 演算子です。
12.12.12.1 is-type 演算子
is-type 演算子は、オブジェクトの実行時の型が特定の型と互換性があるかどうかを確認するために使用されます。 チェックは実行時に実行されます。 E
が式で、T
がdynamic
以外の型である操作E is T
の結果は、E
が null 以外であり、参照変換、ボックス化変換、ボックス化解除変換、ラップ解除変換、またはラップ解除変換によって型T
に正常に変換できるかどうかを示すブール値です。
操作は次のように評価されます。
E
が匿名関数またはメソッド グループの場合、コンパイル時エラーが発生しますE
がnull
リテラルの場合、またはE
の値がnull
の場合、結果はfalse
。- それ以外:
R
E
のランタイム型にします。D
次のようにR
から派生します。R
が null 許容値型の場合、D
はR
の基になる型です。- それ以外の場合、
D
はR
です。 - 結果は、次のように
D
とT
によって異なります。 T
が参照型の場合、結果は次の場合にtrue
されます。D
とT
の間に ID 変換が存在するD
は参照型であり、D
からT
への暗黙的な参照変換が存在します。- どちらか:
D
は値型であり、D
からT
へのボックス化変換が存在します。
または、D
は値型であり、T
はD
によって実装されるインターフェイス型です。
T
が null 許容値型の場合、D
がT
の基になる型である場合、結果はtrue
されます。T
が null 非許容値型の場合、D
とT
が同じ型の場合、結果はtrue
されます。- それ以外の場合、結果は
false
です。
ユーザー定義の変換は、 is
演算子では考慮されません。
注:
is
演算子は実行時に評価されるため、すべての型引数が置換されており、考慮すべきオープン型 (§8.4.3) はありません。 end note
注:
is
演算子は、コンパイル時の型と変換の観点から次のように理解できます。ここで、C
はE
のコンパイル時の型です。
e
のコンパイル時の型がT
と同じ場合、 または、暗黙的な参照変換 (§10.2.8)、ボックス化変換 (§10.2.9)、折り返し変換 (§§ 10.6)、または明示的なアンラップ変換 (§10.6) が、E
のコンパイル時の型からT
に存在します。
C
が null 非許容値型の場合、操作の結果はtrue
。- それ以外の場合、操作の結果は
E != null
の評価と同じです。- それ以外の場合、明示的な参照変換 (§10.3.5) またはボックス化解除変換 (§10.3.7) が
C
からT
に存在する場合、またはC
またはT
がオープン型 (§8.4.3) である場合は、上記のランタイム チェックが実行されます。- それ以外の場合、
E
から型T
への参照、ボックス化、折り返し、またはラップ解除の変換はできず、操作の結果はfalse
。 コンパイラは、コンパイル時の型に基づいて最適化を実装できます。end note
12.12.12.2 is-pattern 演算子
is-pattern 演算子は、式によって計算された値が特定のパターン (§11matches) かどうかを確認するために使用されます。 チェックは実行時に実行されます。 値がパターンと一致する場合、is-pattern 演算子の結果は true になります。それ以外の場合は false です。
E
がT
型の関係式であり、P
がパターンであるフォーム E is P
の式の場合、次のいずれかが保持されている場合はコンパイル時エラーになります。
E
は値を指定しないか、型を持っていません。- パターン
P
は、型T
に適用できません (§11.2)。
12.12.13 as 演算子
as
演算子は、値を特定の参照型または null 許容値型に明示的に変換するために使用されます。 キャスト式 (§12.9.7) とは異なり、 as
演算子は例外をスローしません。 代わりに、指定された変換が不可能な場合、結果の値は null
。
フォーム E as T
の操作では、 E
は式で、 T
は参照型、参照型と呼ばれる型パラメーター、または null 許容値型である必要があります。 さらに、次のうち少なくとも 1 つが true であるか、それ以外の場合はコンパイル時エラーが発生します。
- ID (§10.2.2)、暗黙的な null 許容 (§10.2.6)、暗黙的参照 (§10.2.8)、ボックス化 (§10.2.9) )、明示的な null 許容 (§10.3.4)、明示的な参照 (§10.3.5)、またはラップ (§8.3.12) 変換が
E
からT
に存在します。 E
またはT
の型は開いている型です。E
はnull
リテラルです。
E
のコンパイル時の型がdynamic
されていない場合、操作E as T
と同じ結果が生成されます
E is T ? (T)(E) : (T)null
ただし、E
が評価されるのは 1 回だけです。 コンパイラは、上記の拡張によって示される 2 つのランタイム型チェックとは対照的に、最大 1 つのランタイム型チェックを実行するように E as T
を最適化することが期待できます。
キャスト演算子とは異なり、 E
のコンパイル時の型が dynamic
場合、 as
演算子は動的にバインドされません (§12.3.3)。 したがって、この場合の拡張は次のとおりです。
E is T ? (T)(object)(E) : (T)null
ユーザー定義の変換など、一部の変換は as
演算子では実行できず、代わりにキャスト式を使用して実行する必要があることに注意してください。
例: この例では
class X { public string F(object o) { return o as string; // OK, string is a reference type } public T G<T>(object o) where T : Attribute { return o as T; // Ok, T has a class constraint } public U H<U>(object o) { return o as U; // Error, U is unconstrained } }
G
の型パラメーターT
は、クラス制約があるため、参照型であることがわかっています。 ただし、H
の型パラメーターU
は使用されないため、H
でのas
演算子の使用は許可されません。end の例
12.13 論理演算子
12.13.1 全般
&,
^
演算子と|
演算子は論理演算子と呼ばれます。
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
論理演算子のオペランドにコンパイル時の型 dynamic
がある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamic
され、コンパイル時の型が dynamic
されているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。
フォーム x «op» y
の演算の場合、«op» は論理演算子の 1 つであり、オーバーロード解決 (§12.4.5) が適用され、特定の演算子の実装が選択されます。 オペランドは選択した演算子のパラメーター型に変換され、結果の型は演算子の戻り値の型になります。
定義済みの論理演算子については、次のサブクラスで説明します。
12.13.2 整数論理演算子
定義済みの整数論理演算子は次のとおりです。
int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);
int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);
int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);
&
演算子は、2 つのオペランドのビットごとの論理 AND を計算し、|
演算子は 2 つのオペランドのビットごとの論理 OR を計算し、^
演算子は 2 つのオペランドのビットごとの論理排他的 OR を計算します。 これらの操作からのオーバーフローは発生しません。
上記で定義した、リフトされていない定義済みの整数論理演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.13.3 列挙論理演算子
すべての列挙型 E
は、次の定義済みの論理演算子を暗黙的に提供します。
E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);
x «op» y
を評価した結果(x
とy
は基になる型U
を持つ列挙型E
の式であり、«op» は論理演算子の 1 つであり、(E)((U)x «op» (U)y)
の評価とまったく同じです。 つまり、列挙型の論理演算子は、2 つのオペランドの基になる型に対して論理演算を実行するだけです。
上記で定義した、リフトされていない定義済み列挙論理演算子のリフト (§12.4.8) フォームも事前に定義されています。
12.13.4 ブール論理演算子
定義済みのブール論理演算子は次のとおりです。
bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
x
と y
の両方が true
であれば、x & y
の結果は true
です。 それ以外の場合、結果は false
です。
x
またはy
がtrue
されている場合、x | y
の結果はtrue
されます。 それ以外の場合、結果は false
です。
x ^ y
の結果は、x
がtrue
され、y
がfalse
されている場合、またはx
がfalse
され、y
がtrue
場合にtrue
されます。 それ以外の場合、結果は false
です。 オペランドが bool
型の場合、 ^
演算子は !=
演算子と同じ結果を計算します。
12.13.5 Null 許容ブール値 & |演算子
null 許容ブール型 bool?
は、 true
、 false
、および null
の 3 つの値を表すことができます。
他の二項演算子と同様に、論理演算子の &
と |
のリフト形式 (§12.13.4) も事前に定義されています。
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
リフトされた &
演算子と |
演算子のセマンティクスは、次の表で定義します。
x |
y |
x & y |
x \| y |
---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
true |
null |
null |
true |
false |
true |
false |
true |
false |
false |
false |
false |
false |
null |
false |
null |
null |
true |
null |
true |
null |
false |
false |
null |
null |
null |
null |
null |
注:
bool?
型は概念的には、SQL のブール式に使用される 3 つの値を持つ型に似ています。 上の表は SQL と同じセマンティクスに従います。一方、§12.4.8 の規則を&
および|
演算子には適用しません。 §12.4.8 の規則ではリフトされた^
演算子に SQL に似たセマンティクスが既に提供されています。 end note
12.14 条件付き論理演算子
12.14.1 全般
&&
演算子と ||
演算子は、条件付き論理演算子と呼ばれます。 これらは、"短絡" 論理演算子とも呼ばれます。
conditional_and_expression
: inclusive_or_expression
| conditional_and_expression '&&' inclusive_or_expression
;
conditional_or_expression
: conditional_and_expression
| conditional_or_expression '||' conditional_and_expression
;
&&
演算子と||
演算子は、&
演算子と|
演算子の条件付きバージョンです。
- 操作
x && y
は操作x & y
に対応します。ただし、y
はx
がfalse
されていない場合にのみ評価されます。 - 操作
x || y
は操作x | y
に対応します。ただし、y
はx
がtrue
されていない場合にのみ評価されます。
注: ショート サーキットで "not true" 条件と "not false" 条件が使用される理由は、ユーザー定義の条件付き演算子でショートサーキットが適用されるタイミングを定義できるようにするためです。 ユーザー定義型は、
operator true
がfalse
を返し、operator false
がfalse
を返す状態になる可能性があります。 そのような場合、&&
も||
もショートしません。 end note
条件論理演算子のオペランドにコンパイル時の型 dynamic
がある場合、式は動的にバインドされます (§12.3.3)。 この場合、式のコンパイル時の型が dynamic
され、コンパイル時の型が dynamic
されているオペランドの実行時型を使用して、以下で説明する解決が実行時に行われます。
フォーム x && y
またはx || y
の操作は、操作がx & y
またはx | y
書き込まれたかのようにオーバーロード解決 (§12.4.5) を適用することによって処理されます。 このとき、次のようになります。
- オーバーロードの解決で 1 つの最適な演算子が見つからない場合、またはオーバーロード解決で定義済みの整数論理演算子または null 許容ブール論理演算子 (§12.13.5) のいずれかを選択すると、バインド時エラーが発生します。
- それ以外の場合、選択した演算子が定義済みのブール論理演算子 (§12.13.4) の 1 つである場合、操作は §12.14.2 で説明されているように処理されます。
- それ以外の場合、選択した演算子はユーザー定義演算子であり、操作は §12.14.3 で説明されているように処理されます。
条件付き論理演算子を直接オーバーロードすることはできません。 ただし、条件付き論理演算子は通常の論理演算子の観点から評価されるため、通常の論理演算子のオーバーロードは、特定の制限があり、条件付き論理演算子のオーバーロードとも見なされます。 これについては、 §12.14.3 で詳しく説明します。
12.14.2 ブール条件論理演算子
&&
または||
のオペランドがbool
型の場合、またはオペランドが該当するoperator &
またはoperator |
を定義せず、bool
への暗黙的な変換を定義する型の場合、演算は次のように処理されます。
- 操作
x && y
はx ? y : false
として評価されます。 つまり、x
は最初に評価され、bool
型に変換されます。 次に、x
がtrue
されると、y
が評価され、型bool
に変換され、これが操作の結果になります。 それ以外の場合、操作の結果はfalse
。 - 操作
x || y
はx ? true : y
として評価されます。 つまり、x
は最初に評価され、bool
型に変換されます。 次に、x
がtrue
場合、操作の結果はtrue
。 それ以外の場合、y
は評価され、bool
型に変換され、これが操作の結果になります。
12.14.3 ユーザー定義の条件付き論理演算子
&&
または||
のオペランドが、適用可能なユーザー定義のoperator &
またはoperator |
を宣言する型である場合、次の両方が true になります。ここで、T
は、選択した演算子が宣言されている型です。
- 選択した演算子の各パラメーターの戻り値の型と型は
T
。 つまり、演算子は、T
型の 2 つのオペランドの論理 AND または論理 OR を計算し、T
型の結果を返します。 T
は、operator true
とoperator false
の宣言を含む必要があります。
これらの要件のいずれかが満たされていない場合、バインド時エラーが発生します。 それ以外の場合、 &&
または ||
操作は、ユーザー定義の operator true
または operator false
を選択したユーザー定義演算子と組み合わせることによって評価されます。
- 操作
x && y
はT.false(x) ? x : T.&(x, y)
として評価されます。ここで、T.false(x)
はT
で宣言されたoperator false
の呼び出しであり、T.&(x, y)
は選択したoperator &
の呼び出しです。 つまり、x
が最初に評価され、結果に対してoperator false
が呼び出され、x
が確実に false であるかどうかを判断します。 次に、x
が確実に false の場合、操作の結果は、x
に対して以前に計算された値になります。 それ以外の場合は、y
が評価され、選択したoperator &
が、x
に対して以前に計算された値と、操作の結果を生成するためにy
のために計算された値に対して呼び出されます。 - 操作
x || y
はT.true(x) ? x : T.|(x, y)
として評価されます。ここで、T.true(x)
はT
で宣言されたoperator true
の呼び出しであり、T.|(x, y)
は選択したoperator |
の呼び出しです。 つまり、x
が最初に評価され、結果に対してoperator true
が呼び出され、x
が確実に当てはまるかどうかを判断します。 次に、x
が確実に true の場合、操作の結果は、x
に対して以前に計算された値になります。 それ以外の場合は、y
が評価され、選択したoperator |
が、x
に対して以前に計算された値と、操作の結果を生成するためにy
のために計算された値に対して呼び出されます。
いずれの操作でも、 x
によって指定された式は 1 回だけ評価され、 y
によって指定された式は評価されず、1 回だけ評価もされません。
12.15 null 合体演算子
??
演算子は、null 合体演算子と呼ばれます。
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
フォーム a ?? b
の null 結合式では、 a
がnull
でない場合、結果は a
されます。それ以外の場合、結果は b
。 操作は、a
がnull
されている場合にのみ、b
を評価します。
null 合体演算子は右連想です。つまり、操作は右から左にグループ化されます。
例: フォーム
a ?? b ?? c
の式は、?? (b ?? c)
として評価されます。 一般に、フォームE1 ?? E2 ?? ... ?? EN
の式は、null
でないオペランドの最初の式を返します。また、すべてのオペランドがnull
されている場合はnull
します。 end の例
a ?? b
式の型は、オペランドで使用できる暗黙的な変換によって異なります。 優先順に、 a ?? b
の型はA₀
、A
、またはB
です a
。A
はa
の型 (型を持つ場合)、B
はb
の型 (b
に型がある場合)、A₀
は null 許容値型の場合は基になるA
の型A
。それ以外の場合はA
。 具体的には、 a ?? b
は次のように処理されます。
A
が存在し、null 許容値型または参照型でない場合は、コンパイル時エラーが発生します。- それ以外の場合、
A
が存在し、b
が動的な式である場合、結果の型はdynamic
。 実行時に、a
が最初に評価されます。a
がnull
されていない場合、a
はdynamic
に変換され、これが結果になります。 それ以外の場合、b
が評価され、これが結果になります。 - それ以外の場合、
A
が存在し、null 許容値型であり、b
からA₀
への暗黙的な変換が存在する場合、結果の型はA₀
。 実行時に、a
が最初に評価されます。a
がnull
されていない場合、a
はA₀
型にラップ解除され、結果になります。 それ以外の場合は、b
が評価され、A₀
型に変換され、これが結果になります。 - それ以外の場合、
A
が存在し、b
からA
への暗黙的な変換が存在する場合、結果の型はA
。 実行時に、a が最初に評価されます。 a が null でない場合、a は結果になります。 それ以外の場合は、b
が評価され、A
型に変換され、これが結果になります。 - それ以外の場合、
A
が存在し、null 許容値型である場合、b
は型B
を持ち、A₀
からB
への暗黙的な変換が存在する場合、結果の型はB
。 実行時に、a
が最初に評価されます。a
がnull
されていない場合、a
はA₀
型にラップ解除され、B
型に変換され、これが結果になります。 それ以外の場合、b
が評価され、結果になります。 - それ以外の場合、
b
に型B
があり、a
からB
への暗黙的な変換が存在する場合、結果の型はB
。 実行時に、a
が最初に評価されます。a
がnull
されていない場合、a
は型B
に変換され、これが結果になります。 それ以外の場合、b
が評価され、結果になります。
それ以外の場合は、 a
と b
に互換性がありません。 a
コンパイル時エラーが発生します。
12.16 throw 式演算子
throw_expression
: 'throw' null_coalescing_expression
;
throw_expressionは、null_coalescing_expressionを評価することによって生成された値をスローします。 式は暗黙的に System.Exception
に変換でき、式を評価した結果はスローされる前に System.Exception
に変換されます。 throw 式の評価実行時の動作は、throw ステートメント (§13.10.6) に指定されているものと同じです。
throw_expressionには型がありません。 throw_expressionは、単純スロー変換によってすべての型に変換できます。
throw 式は、次の構文コンテキストでのみ発生します。
- 三項条件演算子 (
?:
) の 2 番目または 3 番目のオペランドとして。 - null 合体演算子の 2 番目のオペランド (
??
)。 - 式形式のラムダまたはメンバーの本体。
12.17 宣言式
宣言式は、ローカル変数を宣言します。
declaration_expression
: local_variable_type identifier
;
local_variable_type
: type
| 'var'
;
simple_name _
は、単純な名前検索で関連付けられた宣言が見つからなかった場合にも宣言式と見なされます (§12.8.4)。 宣言式として使用する場合、 _
は simple 破棄と呼ばれます。 意味的には var _
に相当しますが、より多くの場所で許可されます。
宣言式は、次の構文コンテキストでのみ発生します。
- argument_listの
out
argument_valueとして。 - 単純な破棄
_
として、単純な割り当ての左側 (§12.21.2) で構成されます。 - 1 つ以上の再帰的に入れ子になったtuple_expressionのtuple_elementとして、最も外側は分解割り当ての左側で構成されます。 deconstruction_expressionは、宣言式が構文的に存在しない場合でも、この位置で宣言式を生み出します。
注: これは、宣言式をかっこで分けることができないことを意味します。 end note
declaration_expressionで宣言された暗黙的に型指定された変数が、宣言されているargument_list内で参照されるのはエラーです。
これは、 declaration_expression で宣言された変数が、それが発生する分解割り当て内で参照されるエラーです。
単純な破棄、またはlocal_variable_typeが単純に型指定された変数として分類var
識別子である宣言式。 式には型がなく、ローカル変数の型は次のように構文コンテキストに基づいて推論されます。
- argument_listでは、変数の推論された型は、対応するパラメーターの宣言された型です。
- 単純な代入の左側として、変数の推論された型は代入の右側の型です。
- 単純な代入の左側の tuple_expression では、変数の推論された型は、代入の右側 (分解後) の対応するタプル要素の型です。
それ以外の場合、宣言式は 明示的に型指定された変数 変数として分類され、式の型と宣言された変数は、 local_variable_typeによって指定されるものとします。
識別子 _
を持つ宣言式は破棄 (§9.2.9.1) であり、変数の名前は導入されません。 _
以外の識別子を持つ宣言式は、その名前を最も近い外側のローカル変数宣言空間 (§7.3) に導入します。
例:
string M(out int i, string s, out bool b) { ... } var s1 = M(out int i1, "One", out var b1); Console.WriteLine($"{i1}, {b1}, {s1}"); // Error: i2 referenced within declaring argument list var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); var s3 = M(out int _, "Three", out var _);
s1
の宣言は、明示的に型指定された宣言式と暗黙的に型指定された宣言式の両方を示します。 推論されたb1
の型は、M1
の対応する出力パラメーターの型であるため、bool
されます。 後続のWriteLine
は、外側のスコープに導入された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 _
の使用を示します。end の例
12.18 条件演算子
?:
演算子は条件付き演算子と呼ばれます。 時には三項演算子とも呼ばれます。
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' expression ':' expression
| null_coalescing_expression '?' 'ref' variable_reference ':'
'ref' variable_reference
;
ref
が存在する場合、条件演算子では throw 式 (§12.16) は使用できません。
フォーム b ? x : y
の条件式は、最初に条件 b
を評価します。 次に、 b
が true
されると、 x
が評価され、操作の結果になります。 それ以外の場合、 y
が評価され、操作の結果になります。 条件式では、 x
と y
の両方が評価されることはありません。
条件演算子は右連想です。つまり、操作は右から左にグループ化されます。
例:
a ? b : c ? d : e
形式の式は、a ? b : (c ? d : e)
として評価されます。 end の例
?:
演算子の最初のオペランドは、暗黙的にbool
に変換できる式、またはoperator true
を実装する型の式である必要があります。 どちらの要件も満たされていない場合は、コンパイル時エラーが発生します。
ref
が存在する場合:
- ID 変換は、2 つの variable_referenceの型の間に存在するものとし、結果の型はどちらの型でもかまいません。 いずれかの型が
dynamic
の場合、型推論はdynamic
を優先します (§8.7)。 いずれかの型がタプル型 (§8.3.11) の場合、同じ序数位置の要素名が両方のタプルで一致する場合、型推論には要素名が含まれます。 - 結果は変数参照であり、両方の variable_referenceが書き込み可能な場合は書き込み可能です。
注:
ref
が存在する場合、 conditional_expression は変数参照を返します。これは、= ref
演算子を使用して参照変数に割り当てたり、参照/入力/出力パラメーターとして渡したりできます。 end note
ref
が存在しない場合、?:
演算子の 2 番目と 3 番目のオペランド (x
とy
) は、条件式の型を制御します。
x
に型X
があり、y
に型Y
がある場合は、X
とY
の間に 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
は条件式の型になります。 - それ以外の場合は、式の型を特定できなくなり、コンパイル時エラーが発生します。
x
とy
の 1 つだけに型があり、x
とy
の両方がその型に暗黙的に変換できる場合、それが条件式の型になります。- それ以外の場合は、式の型を特定できなくなり、コンパイル時エラーが発生します。
フォーム b ? ref x : ref y
の ref 条件式の実行時処理は、次の手順で構成されます。
- まず、
b
が評価され、b
のbool
値が決定されます。b
の型からbool
への暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、bool
値が生成されます。- それ以外の場合は、
b
の型によって定義されたoperator true
が呼び出され、bool
値が生成されます。
- 上記の手順で生成された
bool
値がtrue
場合、x
が評価され、結果の変数参照が条件式の結果になります。 - それ以外の場合、
y
が評価され、結果の変数参照が条件式の結果になります。
フォーム b ? x : y
の条件式の実行時処理は、次の手順で構成されます。
- まず、
b
が評価され、b
のbool
値が決定されます。b
の型からbool
への暗黙的な変換が存在する場合は、この暗黙的な変換が実行され、bool
値が生成されます。- それ以外の場合は、
b
の型によって定義されたoperator true
が呼び出され、bool
値が生成されます。
- 上記の手順で生成された
bool
値がtrue
場合、x
が評価され、条件式の型に変換され、これが条件式の結果になります。 - それ以外の場合、
y
は評価され、条件式の型に変換され、これが条件式の結果になります。
12.19 匿名関数式
12.19.1 全般
不要な関数は、"インライン" メソッド定義を表す式です。 匿名関数自体には値や型はありませんが、互換性のあるデリゲート型または式ツリー型に変換できます。 匿名関数変換の評価は、変換のターゲット型によって異なります。デリゲート型の場合、匿名関数が定義するメソッドを参照するデリゲート値に変換が評価されます。 式ツリー型の場合、変換はメソッドの構造をオブジェクト構造として表す式ツリーに評価されます。
注: 歴史的な理由から、匿名関数には、 lambda_expressionと anonymous_method_expressionという 2 種類の構文があります。 ほぼすべての目的で、 lambda_expressionは anonymous_method_expressionよりも簡潔で表現力が高く、下位互換性のために言語に残っています。 end note
lambda_expression
: 'async'? anonymous_function_signature '=>' anonymous_function_body
;
anonymous_method_expression
: 'async'? 'delegate' explicit_anonymous_function_signature? block
;
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
;
explicit_anonymous_function_signature
: '(' explicit_anonymous_function_parameter_list? ')'
;
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter
(',' explicit_anonymous_function_parameter)*
;
explicit_anonymous_function_parameter
: anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
implicit_anonymous_function_signature
: '(' implicit_anonymous_function_parameter_list? ')'
| implicit_anonymous_function_parameter
;
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter
(',' implicit_anonymous_function_parameter)*
;
implicit_anonymous_function_parameter
: identifier
;
anonymous_function_body
: null_conditional_invocation_expression
| expression
| 'ref' variable_reference
| block
;
null_conditional_invocation_expressionと式の両方が適用される場合にanonymous_function_bodyを認識する場合前者を選択する必要があります。
注: ここでの代替手段の重複と優先順位は、説明上の便宜上のみであり、文法規則を詳しく説明して重複を取り除く場合があります。 ANTLR やその他の文法システムでは、同じ利便性が採用されているため、 anonymous_function_body には指定されたセマンティクスが自動的に適用されます。 end note
注: 式として扱われる場合、
M
の結果の型がvoid
(§12.8.13) の場合、x?.M()
などの構文形式はエラーになります。 ただし、 null_conditional_invocation_expressionとして扱われる場合、結果の型はvoid
できます。 end note
例:
List<T>.Reverse
の結果の型がvoid
。 次のコードでは、匿名式の本文は null_conditional_invocation_expressionであるため、エラーではありません。Action<List<int>> a = x => x?.Reverse();
end の例
=>
演算子は代入 (=
) と同じ優先順位を持ち、右連想です。
async
修飾子を持つ匿名関数は非同期関数であり、§15.15 で説明されている規則に従います。
lambda_expressionの形式の匿名関数のパラメーターは、明示的または暗黙的に型指定できます。 明示的に型指定されたパラメーター リストでは、各パラメーターの型が明示的に指定されます。 暗黙的に型指定されたパラメーター リストでは、匿名関数が発生するコンテキストからパラメーターの型が推論されます。具体的には、匿名関数が互換性のあるデリゲート型または式ツリー型に変換されると、その型はパラメーター型 (§10.7) を提供します。
暗黙的に型指定された単一のパラメーターを持つ lambda_expression では、パラメーター リストからかっこを省略できます。 つまり、フォームの匿名関数です。
( «param» ) => «expr»
を省略できます。
«param» => «expr»
anonymous_method_expressionの形式の匿名関数のパラメーター リストは省略可能です。 指定した場合、パラメーターは明示的に型指定されます。 そうでない場合、匿名関数は、出力パラメーターを含まないパラメーター リストを持つデリゲートに変換できます。
匿名関数の ブロック 本体は常に到達可能です (§13.2)。
例: 匿名関数の例をいくつか次に示します。
x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, block body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, block body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters async (t1,t2) => await t1 + await t2 // Async delegate (int x) { return x + 1; } // Anonymous method expression delegate { return 1 + 1; } // Parameter list omitted
end の例
lambda_expressionとanonymous_method_expressionの動作は、次の点を除いて同じです。
- anonymous_method_expressionパラメーター リストを完全に省略すると、値パラメーターの任意のリストのデリゲート型への変換が可能になります。
- lambda_expressionではパラメーター型を省略して推論できますが、 anonymous_method_expressionではパラメーター型を明示的に指定する必要があります。
- lambda_expressionの本体は式またはブロックにすることができますが、anonymous_method_expressionの本体はブロックになります。
- 互換性のある式ツリー型 (§8.6) に変換できるのは、lambda_expressionだけです。
12.19.2 匿名関数シグネチャ
匿名関数の anonymous_function_signature は、名前と、必要に応じて匿名関数のパラメーターの型を定義します。 匿名関数のパラメーターのスコープは、 anonymous_function_body (§7.7) です。 パラメーター リスト (指定されている場合) と共に、anonymous-method-body は宣言空間 (§7.3) を構成します。 したがって、匿名関数のパラメーターの名前が、 anonymous_method_expression または lambda_expressionを含むスコープを持つローカル変数、ローカル定数、またはパラメーターの名前と一致するコンパイル時エラーになります。
匿名関数に explicit_anonymous_function_signatureがある場合、互換性のあるデリゲート型と式ツリー型のセットは、同じ順序で同じパラメーター型と修飾子を持つものに制限されます (§10.7)。 メソッド グループ変換 (§10.8) とは対照的に、匿名関数パラメーター型の反分散はサポートされていません。 匿名関数に anonymous_function_signatureがない場合、互換性のあるデリゲート型と式ツリー型のセットは、出力パラメーターのない型に制限されます。
anonymous_function_signatureに属性やパラメーター配列を含めることはできません。 ただし、 anonymous_function_signature は、パラメーター リストにパラメーター配列が含まれるデリゲート型と互換性がある場合があります。
また、互換性がある場合でも、式ツリー型への変換はコンパイル時に失敗する可能性があることに注意してください (§8.6)。
12.19.3 匿名関数本体
匿名関数の本体 (expression または block) には、次の規則が適用されます。
- 匿名関数にシグネチャが含まれている場合は、シグネチャで指定されたパラメーターを本文で使用できます。 匿名関数にシグネチャがない場合は、パラメーター (§10.7) を持つデリゲート型または式型に変換できますが、本文ではパラメーターにアクセスできません。
- 最も近い外側の匿名関数のシグネチャ (存在する場合) で指定された参照渡しパラメーターを除き、本文が参照渡しパラメーターにアクセスするのはコンパイル時エラーです。
- 最も近い外側の匿名関数のシグネチャで指定されたパラメーター (存在する場合) を除き、本文が
ref struct
型のパラメーターにアクセスするのはコンパイル時エラーです。 this
の型が構造体型の場合、本文がthis
にアクセスするためのコンパイル時エラーです。 これは、アクセスが明示的 (this.x
と同様) か暗黙的か (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.UnitCount
がFunc<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
メソッドを選択します。end の例
12.19.5 匿名関数と動的バインディング
匿名関数は、動的にバインドされた演算の受信者、引数、またはオペランドにすることはできません。
12.19.6 外部変数
12.19.6.1 全般
スコープに lambda_expression または anonymous_method_expression が含まれるローカル変数、値パラメーター、またはパラメーター配列は、匿名関数の outer 変数 と呼ばれます。 クラスのインスタンス関数メンバーでは、この値は値パラメーターと見なされ、関数メンバーに含まれるすべての匿名関数の外部変数です。
12.19.6.2 キャプチャされた外部変数
外部変数が匿名関数によって参照されている場合、外部変数は匿名関数によって キャプチャ されていると言われます。 通常、ローカル変数の有効期間は、関連付けられているブロックまたはステートメントの実行に制限されます (§9.2.9)。 ただし、キャプチャされた外部変数の有効期間は、少なくとも匿名関数から作成されたデリゲートまたは式ツリーがガベージ コレクションの対象になるまで延長されます。
例: この例では
delegate int D(); class Test { static D F() { int x = 0; D result = () => ++x; return result; } static void Main() { D d = F(); Console.WriteLine(d()); Console.WriteLine(d()); Console.WriteLine(d()); } }
ローカル変数
x
は匿名関数によってキャプチャされ、x
の有効期間は、少なくともF
から返されたデリゲートがガベージ コレクションの対象になるまで拡張されます。 匿名関数の各呼び出しは、x
の同じインスタンスで動作するため、例の出力は次のようになります。1 2 3
end の例
ローカル変数または値パラメーターが匿名関数によってキャプチャされる場合、ローカル変数またはパラメーターは固定変数 (§23.4) と見なされなくなりますが、代わりに移動可能変数と見なされます。 ただし、キャプチャされた外部変数は fixed
ステートメント (§23.7) では使用できないため、キャプチャされた外部変数のアドレスを取得できません。
注: キャプチャされていない変数とは異なり、キャプチャされたローカル変数を複数の実行スレッドに同時に公開できます。 end note
12.19.6.3 ローカル変数のインスタンス化
ローカル変数は、実行が変数のスコープに入るとインスタンス化されていると見なされます。
例: たとえば、次のメソッドが呼び出されると、
x
ローカル変数がインスタンス化され、ループの反復処理ごとに 1 回、3 回初期化されます。static void F() { for (int i = 0; i < 3; i++) { int x = i * 2 + 1; ... } }
ただし、
x
の宣言をループの外側に移動すると、x
が 1 回インスタンス化されます。static void F() { int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; ... } }
end の例
キャプチャされない場合は、ローカル変数がインスタンス化される頻度を正確に確認する方法はありません。インスタンス化の有効期間が不整合であるため、各インスタント化で同じストレージの場所を単に使用できます。 ただし、匿名関数がローカル変数をキャプチャすると、インスタンス化の効果が明らかになります。
例: 例
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { int x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
この例では、次のように出力されます。
1 3 5
ただし、
x
の宣言がループの外側に移動された場合:delegate void D(); class Test { static D[] F() { D[] result = new D[3]; int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
出力は次のとおりです。
5 5 5
コンパイラは、3 つのインスタンス化を単一のデリゲート インスタンス (§10.7.2) に最適化することが許可されていることに注意してください (ただし、必須ではありません)。
end の例
for ループで反復変数が宣言されている場合、その変数自体はループの外部で宣言されていると見なされます。
例: 反復変数自体をキャプチャするように例が変更された場合:
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { result[i] = () => Console.WriteLine(i); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
反復変数のインスタンスが 1 つだけキャプチャされ、出力が生成されます。
3 3 3
end の例
匿名関数デリゲートは、キャプチャされた変数の一部を共有しても、別のインスタンスを持つことができます。
例: たとえば、
F
が次の値に変更された場合static D[] F() { D[] result = new D[3]; int x = 0; for (int i = 0; i < 3; i++) { int y = 0; result[i] = () => Console.WriteLine($"{++x} {++y}"); } return result; }
3 つのデリゲートは、
x
の同じインスタンスをキャプチャしますが、y
の個別のインスタンスをキャプチャします。出力は次のとおりです。1 1 2 1 3 1
end の例
個別の匿名関数は、外部変数の同じインスタンスをキャプチャできます。
例: この例では次のようになります。
delegate void Setter(int value); delegate int Getter(); class Test { static void Main() { int x = 0; Setter s = (int value) => x = value; Getter g = () => x; s(5); Console.WriteLine(g()); s(10); Console.WriteLine(g()); } }
2 つの匿名関数は、ローカル変数
x
の同じインスタンスをキャプチャするため、その変数を介して "通信" できます。 この例の出力は次のとおりです。5 10
end の例
12.19.7 匿名関数式の評価
匿名関数 F
は、常にデリゲート型 D
または式ツリー型 E
に、直接またはデリゲート作成式 new D(F)
の実行によって変換されます。 この変換は、 §10.7 で説明されているように、匿名関数の結果を決定します。
12.19.8 実装例
このサブクラスは有益です。
このサブクラウズでは、他の C# コンストラクトの観点から匿名関数変換を実装する可能性について説明します。 ここで説明する実装は、商用 C# コンパイラで使用されるのと同じ原則に基づいていますが、決して必須の実装ではなく、可能な唯一の実装でもありません。 式ツリーへの変換については、厳密なセマンティクスがこの仕様の範囲外であるため、簡単に説明するだけです。
このサブドキュメントの残りの部分では、異なる特性を持つ匿名関数を含むコードの例をいくつか示します。 各例では、他の C# コンストラクトのみを使用するコードへの対応する翻訳が提供されます。 この例では、識別子 D
は、次のデリゲート型を表していると見なされます。
public delegate void D();
匿名関数の最も簡単な形式は、外部変数をキャプチャしない関数です。
delegate void D();
class Test
{
static void F()
{
D d = () => Console.WriteLine("test");
}
}
これは、匿名関数のコードが配置されるコンパイラによって生成された静的メソッドを参照するデリゲートインスタンス化に変換できます。
delegate void D();
class Test
{
static void F()
{
D d = new D(__Method1);
}
static void __Method1()
{
Console.WriteLine("test");
}
}
次の例では、匿名関数は this
のインスタンス メンバーを参照します。
delegate void D();
class Test
{
int x;
void F()
{
D d = () => Console.WriteLine(x);
}
}
これは、匿名関数のコードを含むコンパイラ生成インスタンス メソッドに変換できます。
delegate void D();
class Test
{
int x;
void F()
{
D d = new D(__Method1);
}
void __Method1()
{
Console.WriteLine(x);
}
}
この例では、匿名関数はローカル変数をキャプチャします。
delegate void D();
class Test
{
void F()
{
int y = 123;
D d = () => Console.WriteLine(y);
}
}
ローカル変数の有効期間を、少なくとも匿名関数デリゲートの有効期間に拡張する必要があります。 これは、ローカル変数をコンパイラによって生成されたクラスのフィールドに "ホイスト" することによって実現できます。 ローカル変数 (§12.19.6.3) のインスタンス化は、コンパイラによって生成されたクラスのインスタンスの作成に対応し、ローカル変数へのアクセスは、コンパイラによって生成されたクラスのインスタンス内のフィールドへのアクセスに対応します。 さらに、匿名関数は、コンパイラによって生成されたクラスのインスタンス メソッドになります。
delegate void D();
class Test
{
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1()
{
Console.WriteLine(y);
}
}
}
最後に、次の匿名関数は、 this
と、有効期間が異なる 2 つのローカル変数をキャプチャします。
delegate void D();
class Test
{
int x;
void F()
{
int y = 123;
for (int i = 0; i < 10; i++)
{
int z = i * 2;
D d = () => Console.WriteLine(x + y + z);
}
}
}
ここでは、異なるブロック内のローカルが独立した有効期間を持つことができるように、ローカルがキャプチャされるブロックごとにコンパイラによって生成されたクラスが作成されます。 内部ブロックのコンパイラによって生成されたクラスである __Locals2
のインスタンスには、ローカル変数の z
と、 __Locals1
のインスタンスを参照するフィールドが含まれます。 外部ブロックのコンパイラによって生成されたクラスである __Locals1
のインスタンスには、ローカル変数の y
と、外側の関数メンバーの this
を参照するフィールドが含まれています。 これらのデータ構造を使用すると、 __Local2
のインスタンスを介してキャプチャされたすべての外部変数に到達することができ、匿名関数のコードをそのクラスのインスタンス メソッドとして実装できます。
delegate void D();
class Test
{
int x;
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++)
{
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1()
{
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
ローカル変数をキャプチャするためにここで適用するのと同じ手法は、匿名関数を式ツリーに変換するときにも使用できます。コンパイラによって生成されたオブジェクトへの参照は式ツリーに格納でき、ローカル変数へのアクセスは、これらのオブジェクトに対するフィールド アクセスとして表すことができます。 この方法の利点は、デリゲートと式ツリーの間で "リフトされた" ローカル変数を共有できる点です。
情報テキストの末尾。
12.20 クエリ式
12.20.1 全般
クエリ式 は、SQL や XQuery などのリレーショナルおよび階層型クエリ言語に似たクエリの言語統合構文を提供します。
query_expression
: from_clause query_body
;
from_clause
: 'from' type? identifier 'in' expression
;
query_body
: query_body_clauses? select_or_group_clause query_continuation?
;
query_body_clauses
: query_body_clause
| query_body_clauses query_body_clause
;
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
;
let_clause
: 'let' identifier '=' expression
;
where_clause
: 'where' boolean_expression
;
join_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression
;
join_into_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression 'into' identifier
;
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
select_or_group_clause
: select_clause
| group_clause
;
select_clause
: 'select' expression
;
group_clause
: 'group' expression 'by' expression
;
query_continuation
: 'into' identifier query_body
;
クエリ式は、 from
句で始まり、 select
句または group
句で終わります。 最初の from
句の後に、0 個以上の from
、 let
、 where
、 join
、または orderby
句を指定できます。 各from
句は、シーケンスの要素に対して範囲を指定する範囲変数を導入するジェネレーターです。 各 let
句には、前の範囲変数を使用して計算された値を表す範囲変数が導入されています。 各 where
句は、結果から項目を除外するフィルターです。 各 join
句は、ソース シーケンスの指定されたキーを別のシーケンスのキーと比較し、一致するペアを生成します。 各 orderby
句は、指定した条件に従って項目を並べ替えます。最後の select
句または group
句は、範囲変数の観点から結果の形状を指定します。 最後に、1 つのクエリの結果を後続のクエリのジェネレーターとして扱うことで、 into
句を使用してクエリを "スプライス" できます。
12.20.2 クエリ式のあいまいさ
クエリ式では、さまざまなコンテキスト キーワード (§6.4.4): ascending
、 by
、 descending
、 equals
、 from
group
、 into
、 join
、 let
、 on
、 orderby
、 select
、 where
。
これらの識別子をキーワードと単純名の両方として使用することによって生じる可能性のあるあいまいさを回避するために、これらの識別子は、"@
" (§6.4.4) でプレフィックスが付いている場合を除き、クエリ式内の任意の場所でキーワードと見なされます。その場合は、識別子と見なされます。 このため、クエリ式は、"from
identifier" で始まり、その後に ";
"、"=
"、",
" を除くトークンが続く任意の式です。
12.20.3 クエリ式の変換
12.20.3.1 全般
C# 言語では、クエリ式の実行セマンティクスは指定されません。 クエリ式は、クエリ式パターン (§12.20.4) に準拠するメソッドの呼び出しに変換されます。 具体的には、クエリ式は、 Where
、 Select
、 SelectMany
、 Join
、 GroupJoin
、 OrderBy
、 OrderByDescending
、 ThenBy
、 ThenByDescending
、 GroupBy
、および Cast
という名前のメソッドの呼び出しに変換されます。 これらのメソッドには、 §12.20.4 で説明されているように、特定のシグネチャと戻り値の型必要があります。 これらのメソッドは、クエリ対象のオブジェクトのインスタンス メソッド、またはオブジェクトの外部にある拡張メソッドである場合があります。 これらのメソッドは、クエリの実際の実行を実装します。
クエリ式からメソッド呼び出しへの変換は、型バインディングまたはオーバーロードの解決が実行される前に発生する構文マッピングです。 クエリ式の翻訳後、結果のメソッド呼び出しは通常のメソッド呼び出しとして処理され、コンパイル時エラーが明らかにされる可能性があります。 これらのエラー条件には、存在しないメソッド、間違った型の引数、型推論が失敗するジェネリック メソッドが含まれますが、これらに限定されません。
クエリ式は、さらなる削減が不可能になるまで、次の翻訳を繰り返し適用することによって処理されます。 翻訳はアプリケーションの順序で一覧表示されます。各セクションでは、前のセクションの翻訳が完全に実行されていることを前提としており、使い果たされると、同じクエリ式の処理でセクションが後で再検討されることはありません。
範囲変数への代入を含めるクエリ式のコンパイル時エラーです。または、参照または出力パラメーターの引数として範囲変数を使用します。
一部の翻訳では、 *で示される一連の識別子を持つ範囲変数が挿入されます。 詳細については、 §12.20.3.8 で説明します。
12.20.3.2 継続を含むクエリ式
クエリ本文に続く継続を含むクエリ式
from «x1» in «e1» «b1» into «x2» «b2»
に変換されます。
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
次のセクションの翻訳では、クエリに継続がないことを前提としています。
例: 例:
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }
は次のように変換されます。
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }
最後の翻訳は次のとおりです。
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
end の例
12.20.3.3 明示的な範囲変数型
範囲変数型を明示的に指定する from
句
from «T» «x» in «e»
に変換されます。
from «x» in ( «e» ) . Cast < «T» > ( )
範囲変数型を明示的に指定する join
句
join «T» «x» in «e» on «k1» equals «k2»
に変換されます。
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
次のセクションの翻訳では、クエリに明示的な範囲変数型がないことを前提としています。
例: 例
from Customer c in customers where c.City == "London" select c
に変換されます。
from c in (customers).Cast<Customer>() where c.City == "London" select c
最後の翻訳は次の内容です。
customers. Cast<Customer>(). Where(c => c.City == "London")
end の例
注: 明示的な範囲変数型は、非ジェネリック
IEnumerable
インターフェイスを実装するコレクションに対してクエリを実行する場合に便利ですが、ジェネリックIEnumerable<T>
インターフェイスには役立ちません。 上記の例では、顧客がArrayList
型の場合です。 end note
12.20.3.4 クエリ式の生成を解除する
フォームのクエリ式
from «x» in «e» select «x»
に変換されます。
( «e» ) . Select ( «x» => «x» )
例: 例
from c in customers select c
に変換されます。
(customers).Select(c => c)
end の例
逆のクエリ式は、ソースの要素を簡単に選択する式です。
注: 翻訳の後のフェーズ (§12.20.3.6 および §12.20.3.7) では、他の変換手順で導入された逆生成クエリをソースに置き換えることで削除されます。 ただし、クエリ式の結果がソース オブジェクト自体にならないようにすることが重要です。 そうしないと、このようなクエリの結果を返すと、プライベート データ (要素配列など) が呼び出し元に誤って公開される可能性があります。 したがって、この手順では、ソースで
Select
を明示的に呼び出すことによって、ソース コードで直接記述された退化クエリを保護します。 その後、これらのメソッドがソース オブジェクト自体を返さないことを保証するのは、Select
およびその他のクエリ演算子の実装者にかかっています。 end note
12.20.3.5 From、let、where、join、orderby 句
2 番目の from
句の後に select
句が続くクエリ式
from «x1» in «e1»
from «x2» in «e2»
select «v»
に変換されます。
( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )
例: 例
from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.Total }
に変換されます。
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )
end の例
2 つ目の from
句の後に、空でないクエリ本文句のセットを含むクエリ本文 Q
を含むクエリ式。
from «x1» in «e1»
from «x2» in «e2»
Q
に変換されます。
from * in («e1») . SelectMany( «x1» => «e2» ,
( «x1» , «x2» ) => new { «x1» , «x2» } )
Q
例: 例
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
に変換されます。
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
最後の翻訳は次の内容です。
customers. SelectMany(c => c.Orders, (c,o) => new { c, o }). OrderByDescending(x => x.o.Total). Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
ここで
x
は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。end の例
let
式とその前の from
句:
from «x» in «e»
let «y» = «f»
...
に変換されます。
from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )
...
例: 例
from o in orders let t = o.Details.Sum(d => d.UnitPrice * d.Quantity) where t >= 1000 select new { o.OrderID, Total = t }
に変換されます。
from * in (orders).Select( o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) where t >= 1000 select new { o.OrderID, Total = t }
最後の翻訳は次の内容です。
orders .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) .Where(x => x.t >= 1000) .Select(x => new { x.o.OrderID, Total = x.t })
ここで
x
は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。end の例
where
式とその前の from
句:
from «x» in «e»
where «f»
...
に変換されます。
from «x» in ( «e» ) . Where ( «x» => «f» )
...
join
句の直後にselect
句が続く
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
に変換されます。
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
例: 例
from c in customersh join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }
に変換されます。
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })
end の例
join
句の後にクエリ本文句が続きます。
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
に変換されます。
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
join
-into
句の直後にselect
句が続く
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
に変換されます。
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
join into
句の後にクエリ本文句が続く
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into *g»
...
に変換されます。
from * in ( «e1» ) . GroupJoin(
«e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...
例: 例
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
に変換されます。
from * in (customers).GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
最後の翻訳は次の内容です。
customers .GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) .Select(x => new { x, n = x.co.Count() }) .Where(y => y.n >= 10) .Select(y => new { y.x.c.Name, OrderCount = y.n })
ここで、
x
とy
は、コンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。end の例
orderby
句とその前の from
句:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
に変換されます。
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
ordering
句で降順インジケーターが指定されている場合は、代わりにOrderByDescending
またはThenByDescending
の呼び出しが生成されます。
例: 例
from o in orders orderby o.Customer.Name, o.Total descending select o
には最終的な翻訳があります
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)
end の例
次の翻訳では、 let
、 where
、 join
句、または orderby
句がなく、各クエリ式に含まれる最初の from
句が 1 つ以下であることを前提としています。
12.20.3.6 Select 句
フォームのクエリ式
from «x» in «e» select «v»
に変換されます。
( «e» ) . Select ( «x» => «v» )
«v»
が識別子«x»
である場合を除き、翻訳は単純です
( «e» )
例: 例
from c in customers.Where(c => c.City == "London") select c
は単に次のように変換されます。
(customers).Where(c => c.City == "London")
end の例
12.20.3.7 Group 句
group
句
from «x» in «e» group «v» by «k»
に変換されます。
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
«v»
が識別子«x»
の場合を除き、翻訳は
( «e» ) . GroupBy ( «x» => «k» )
例: 例
from c in customers group c.Name by c.Country
に変換されます。
(customers).GroupBy(c => c.Country, c => c.Name)
end の例
12.20.3.8 透過的な識別子
特定の翻訳では、*
で示transparent 識別子を持つ範囲変数が挿入されます。 透過的な識別子は、クエリ式変換プロセスの中間ステップとしてのみ存在します。
クエリ変換によって透過的な識別子が挿入されると、さらに変換手順を実行すると、透過的な識別子が匿名関数と匿名オブジェクト初期化子に伝達されます。 これらのコンテキストでは、透過的な識別子には次の動作があります。
- 透過的な識別子が匿名関数のパラメーターとして発生すると、関連付けられた匿名型のメンバーは、匿名関数の本体のスコープ内で自動的に有効になります。
- 透過的な識別子を持つメンバーがスコープ内にある場合、そのメンバーのメンバーもスコープ内にあります。
- 透過的な識別子が匿名オブジェクト初期化子のメンバー宣言子として発生すると、透過的な識別子を持つメンバーが導入されます。
上記の変換手順では、透過的な識別子は、複数の範囲変数を 1 つのオブジェクトのメンバーとしてキャプチャすることを目的として、匿名型と共に常に導入されます。 C# の実装では、匿名型とは異なるメカニズムを使用して複数の範囲変数をグループ化できます。 次の翻訳の例では、匿名型が使用されることを前提とし、透過的な識別子の 1 つの翻訳の可能性を示しています。
例: 例
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.Total }
に変換されます。
from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.Total }
に変換されます。
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })
これは、透過的な識別子が消去されるときに、〘と
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(x => x.o.Total) .Select(x => new { x.c.Name, x.o.Total })
ここで
x
は、コンパイラによって生成された識別子であり、それ以外の場合は非表示でアクセスできません。例を示します。
from c in customers join o in orders on c.CustomerID equals o.CustomerID join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
に変換されます。
from * in (customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
また、この
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }) .Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { c.Name, o.OrderDate, p.ProductName })
最後の翻訳は次の内容です。
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d }) .Join(products, y => y.d.ProductID, p => p.ProductID, (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })
ここで、
x
とy
はコンパイラによって生成される識別子であり、それ以外の場合は非表示でアクセスできません。 end の例
12.20.4 クエリ式パターン
Query 式パターンは、型がクエリ式をサポートするために実装できるメソッドのパターンを確立します。
パブリック メンバー メソッドとパブリックにアクセス可能な拡張メソッドを次のクラス定義に置き換えることができる場合、ジェネリック型 C<T>
はクエリ式パターンをサポートします。 メンバーとアクセス可能な extenson メソッドは、ジェネリック型 C<T>
の "shape" と呼ばれます。 ジェネリック型は、パラメーターと戻り値の型の間の適切な関係を示すために使用されますが、非ジェネリック型のパターンも実装できます。
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>() { ... }
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate) { ... }
public C<U> Select<U>(Func<T,U> selector) { ... }
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector) { ... }
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector) { ... }
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}
class G<K,T> : C<T>
{
public K Key { get; }
}
上記のメソッドは、 Func<T1, R>
および Func<T1, T2, R>
ジェネリック デリゲート型を使用しますが、パラメーターと戻り値の型で同じリレーションシップを持つ他のデリゲート型または式ツリー型も同様によく使用できます。
注:
C<T>
とO<T>
の間で推奨される関係により、OrderBy
またはOrderByDescending
の結果でのみThenBy
メソッドとThenByDescending
メソッドを使用できるようになります。 end note
注:
GroupBy
の結果の推奨される形状。シーケンスのシーケンスです。各内部シーケンスには追加のKey
プロパティがあります。 end note
注: クエリ式は構文マッピングによってメソッド呼び出しに変換されるため、型はクエリ式パターンの一部またはすべてを実装する方法に大きな柔軟性を持ちます。 たとえば、2 つの呼び出し構文が同じであり、匿名関数は両方に変換できるため、メソッドはデリゲートまたは式ツリーを要求できるため、パターンのメソッドをインスタンス メソッドまたは拡張メソッドとして実装できます。 一部のクエリ式パターンのみを実装する型では、型がサポートするメソッドにマップされるクエリ式変換のみがサポートされます。 end note
注:
System.Linq
名前空間は、System.Collections.Generic.IEnumerable<T>
インターフェイスを実装する任意の型のクエリ式パターンの実装を提供します。 end note
12.21 代入演算子
12.21.1 全般
代入演算子の 1 つを含め、すべて変数、プロパティ、イベント、またはインデクサー要素に新しい値を割り当てます。 例外 = ref
、変数参照 (§9.5) を参照変数 (§9.7) に割り当てます。
assignment
: unary_expression assignment_operator expression
;
assignment_operator
: '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
| right_shift_assignment
;
代入の左オペランドは、変数として分類される式、または = ref
、プロパティ アクセス、インデクサー アクセス、イベント アクセス、またはタプルを除く式でなければなりません。 宣言式は左オペランドとして直接許可されませんが、分解代入の評価のステップとして発生する可能性があります。
=
演算子は、simple 代入演算子と呼ばれます。 右側のオペランドの値または値を、左オペランドによって指定された変数、プロパティ、インデクサー要素、またはタプル要素に割り当てます。 単純代入演算子の左オペランドは、イベント アクセスにすることはできません ( §15.8.2 で説明されている場合を除く)。 単純代入演算子については、 §12.21.2 で説明されています。
演算子 = ref
は、 ref 代入演算子と呼ばれます。 右オペランドは、左オペランドで指定された参照変数の参照先である variable_reference (§9.5) になります。 ref 代入演算子については、 §12.21.3 で説明されています。
=
演算子および= ref
演算子以外の代入演算子は、compound 代入演算子と呼ばれます。 これらの演算子は、2 つのオペランドに対して指定された演算を実行し、結果の値を左オペランドによって指定された変数、プロパティ、またはインデクサー要素に割り当てます。 複合代入演算子については、 §12.21.4 で説明されています。
左側のオペランドとしてイベント アクセス式を持つ +=
演算子と -=
演算子は、 event 代入演算子と呼ばれます。 左側のオペランドとしてイベント アクセスを持つ他の代入演算子は有効ではありません。 イベント割り当て演算子については、 §12.21.5 で説明されています。
代入演算子は右自動調整です。つまり、操作は右から左にグループ化されます。
例:
a = b = c
形式の式は、a = (b = c)
として評価されます。 end の例
12.21.2 単純な割り当て
=
演算子は、単純代入演算子と呼ばれます。
単純代入の左側のオペランドがフォーム E.P
または E[Ei]
の場合、 E
にコンパイル時の型 dynamic
がある場合、割り当ては動的にバインドされます (§12.3.3)。 この場合、割り当て式のコンパイル時の型が dynamic
され、次に示す解決は実行時に E
の種類に基づいて実行されます。 左側のオペランドが形式 E[Ei]
で、 Ei
の少なくとも 1 つの要素にコンパイル時の型 dynamic
があり、 E
のコンパイル時の型が配列でない場合、結果のインデクサー アクセスは動的にバインドされますが、コンパイル時のチェックは制限されます (§12.6.5)。
左オペランドがタプルとして分類される単純な代入は、 分解代入とも呼ばれます。 左オペランドのいずれかのタプル要素に要素名がある場合は、コンパイル時エラーが発生します。 左オペランドのいずれかのタプル要素が declaration_expression であり、他の要素が declaration_expression または単純な破棄ではない場合は、コンパイル時エラーが発生します。
単純な代入x = y
の型は、y
のx
への割り当ての型であり、次のように再帰的に決定されます。
x
がタプル式(x1, ..., xn)
であり、y
n
要素 (§12.7) を持つタプル式(y1, ..., yn)
に分解でき、yi
のxi
に対する各割り当てが型Ti
を持つ場合、代入には型(T1, ..., Tn)
があります。- それ以外の場合、
x
が変数として分類されている場合、変数はreadonly
されず、x
は型T
を持ち、y
はT
への暗黙的な変換を持ち、代入には型T
があります。 - それ以外の場合、
x
が暗黙的に型指定された変数 (つまり、暗黙的に型指定された宣言式) として分類され、y
に型T
がある場合、変数の推論された型はT
され、代入には型T
があります。 - それ以外の場合、
x
がプロパティアクセスまたはインデクサーアクセスとして分類されている場合、プロパティまたはインデクサーはアクセス可能なセットアクセサーを持ち、x
は型T
を持ち、y
はT
への暗黙的な変換を持ち、割り当てには型T
があります。 - それ以外の場合、割り当てが有効ではなく、バインド時エラーが発生します。
型T
を持つフォーム x = y
の単純な割り当ての実行時処理は、次の再帰的な手順で構成される型T
を持つy
のx
への割り当てとして実行されます。
x
は、まだ評価されていない場合に評価されます。x
が変数として分類されている場合、y
が評価され、必要に応じて暗黙的な変換 (§10.2) を使用してT
に変換されます。x
によって指定された変数がreference_typeの配列要素である場合は、実行時チェックが実行され、y
に対して計算された値が、x
が要素である配列インスタンスと互換性があることを確認します。y
がnull
されている場合、または暗黙的な参照変換 (§10.2.8) が、y
によって参照されるインスタンスの型から、x
を含む配列インスタンスの実際の要素型に存在する場合、チェックは成功します。 それ以外の場合は、System.ArrayTypeMismatchException
がスローされます。y
の評価と変換によって得られた値は、x
の評価によって指定された場所に格納され、割り当ての結果として生成されます。
x
がプロパティまたはインデクサー アクセスとして分類されている場合:y
は評価され、必要に応じて暗黙的な変換 (§10.2) を使用してT
に変換されます。x
の set アクセサーは、y
の評価と変換に起因する値をその値引数として呼び出します。y
の評価と変換に起因する値は、割り当ての結果として生成されます。
x
がアリティn
を持つタプル(x1, ..., xn)
として分類される場合:y
は、タプル式e
にn
要素を使用して分解されます。- 結果のタプル
t
は、暗黙的なタプル変換を使用してe
をT
に変換することによって作成されます。 - 各
xi
に対して左から右に順番に、t.Itemi
のxi
への割り当てが実行されます。ただし、xi
は再評価されません。 t
は割り当ての結果として生成されます。
注:
x
のコンパイル時の型がdynamic
であり、y
のコンパイル時の型からdynamic
への暗黙的な変換がある場合、実行時の解決は必要ありません。 end note
注: 配列の共分散規則 (§17.6) では、配列型
A[]
の値を配列型B[]
のインスタンスへの参照にすることができます(B
からA
への暗黙的な参照変換が存在する場合)。 これらの規則により、 reference_type の配列要素への割り当てには、割り当てられている値が配列インスタンスと互換性があることを確認するための実行時チェックが必要です。 この例では、string[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchException
最後の割り当てにより、
ArrayList
への参照をstring[]
の要素に格納できないため、System.ArrayTypeMismatchException
がスローされます。end note
struct_typeで宣言されたプロパティまたはインデクサーが代入のターゲットである場合、プロパティまたはインデクサー アクセスに関連付けられているインスタンス式は変数として分類されます。 インスタンス式が値として分類されると、バインド時エラーが発生します。
注: §12.8.7 のため、フィールドにも同じ規則が適用されます。 end note
例: 宣言を指定します。
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }
この例では
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;
p.X
、p.Y
、r.A
、およびr.B
への割り当ては、p
とr
が変数であるために許可されます。 ただし、この例ではRectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;
r.A
とr.B
は変数ではないため、代入はすべて無効です。end の例
12.21.3 Ref 代入
= ref
演算子は、ref 代入演算子と呼ばれます。
左オペランドは、参照変数 (§9.7)、参照パラメーター ( this
以外)、出力パラメーター、または入力パラメーターにバインドする式でなければなりません。 右オペランドは、左オペランドと同じ型の値を指定する variable_reference を生成する式でなければなりません。
左オペランドの ref-safe-context (§9.7.2) が右オペランドの ref-safe-context よりも広い場合、コンパイル時エラーになります。
右オペランドは、ref 代入の時点で確実に割り当てられます。
左オペランドが出力パラメーターにバインドする場合、その出力パラメーターが ref 代入演算子の先頭に確実に割り当てられていない場合はエラーになります。
左オペランドが書き込み可能な ref である場合 (つまり、 ref readonly
ローカルパラメーターまたは入力パラメーター以外のものを指定します)、右オペランドは書き込み可能な variable_referenceになります。 右オペランド変数が書き込み可能な場合、左オペランドは書き込み可能または読み取り専用の ref である可能性があります。
この演算により、左オペランドが右オペランド変数の別名になります。 右オペランド変数が書き込み可能な場合でも、別名を読み取り専用にすることができます。
ref 代入演算子は、割り当てられた型の variable_reference を生成します。 左オペランドが書き込み可能な場合は書き込み可能です。
ref 代入演算子は、右オペランドによって参照されるストレージの場所を読み取らないものとします。
例:
= ref
の使用例を次に示します。public static int M1() { ... } public static ref int M2() { ... } public static ref uint M2u() { ... } public static ref readonly int M3() { ... } public static void Test() { int v = 42; ref int r1 = ref v; // OK, r1 refers to v, which has value 42 r1 = ref M1(); // Error; M1 returns a value, not a reference r1 = ref M2(); // OK; makes an alias r1 = ref M2u(); // Error; lhs and rhs have different types r1 = ref M3(); // error; M3 returns a ref readonly, which r1 cannot honor ref readonly int r2 = ref v; // OK; make readonly alias to ref r2 = ref M2(); // OK; makes an alias, adding read-only protection r2 = ref M3(); // OK; makes an alias and honors the read-only r2 = ref (r1 = ref M2()); // OK; r1 is an alias to a writable variable, // r2 is an alias (with read-only access) to the same variable }
end の例
注:
= ref
演算子を使用してコードを読み取るとき、オペランドの一部としてref
部分を読み取りたくなる可能性があります。 これは、オペランドが条件付き?:
式である場合に特に混乱します。 たとえば、ref int a = ref b ? ref x : ref y;
を読み取る場合は、演算子である= ref
として読み取り、ref int a = ref (b ? ref x : ref y);
の右オペランドb ? ref x : ref y
することが重要です。 重要なこととして、ref b
式は一見すると表示される場合でも、そのステートメントの一部ではありません。 end note
12.21.4 複合代入
複合代入の左オペランドが E.P
形式または E[Ei]
の場合、 E
のコンパイル時型が dynamic
の場合、代入は動的にバインドされます (§12.3.3)。 この場合、割り当て式のコンパイル時の型が dynamic
され、次に示す解決は実行時に E
の種類に基づいて実行されます。 左側のオペランドが形式 E[Ei]
で、 Ei
の少なくとも 1 つの要素にコンパイル時の型 dynamic
があり、 E
のコンパイル時の型が配列でない場合、結果のインデクサー アクセスは動的にバインドされますが、コンパイル時のチェックは制限されます (§12.6.5)。
フォーム x «op»= y
の操作は、2 項演算子のオーバーロード解決 (§12.4.5) を適用することによって処理 x «op» y
。 このとき、次のようになります。
- 選択した演算子の戻り値の型が
x
の型に暗黙的に変換できる場合、x
は 1 回だけ評価される点を除き、操作はx = x «op» y
として評価されます。 - それ以外の場合、選択した演算子が定義済みの演算子の場合、選択した演算子の戻り値の型が
x
の型に明示的に変換可能であり、y
がx
の型に暗黙的に変換できる場合、または演算子がシフト演算子である場合、操作はx = (T)(x «op» y)
として評価されます。T
はx
の型です。ただし、x
は 1 回だけ評価されます。 - それ以外の場合、複合代入は無効であり、バインド時エラーが発生します。
"1 回だけ評価" という用語は、 x «op» y
の評価では、 x
の構成式の結果が一時的に保存され、 x
への割り当てを実行するときに再利用されることを意味します。
例: 割り当て
A()[B()] += C()
では、A
はint[]
を返すメソッドで、B
とC
はint
を返すメソッドであり、メソッドはA
、B
、C
の順序で 1 回だけ呼び出されます。 end の例
複合代入の左オペランドがプロパティ アクセスまたはインデクサー アクセスである場合、プロパティまたはインデクサーには get アクセサーと set アクセサーの両方が含まれます。 そうでない場合は、バインド時エラーが発生します。
上記の 2 番目の規則では、特定のコンテキストでx = (T)(x «op» y)
として評価x «op»= y
を許可します。 この規則は、左オペランドが sbyte
、 byte
、 short
、 ushort
、または char
の型である場合に、定義済みの演算子を複合演算子として使用できるように存在します。 両方の引数がこれらの型のいずれかである場合でも、定義済みの演算子は、§12.4.7.3 で説明されているように、int
型の結果を生成。 したがって、キャストがないと、結果を左オペランドに割り当てできなくなります。
定義済みの演算子に対するルールの直感的な効果は、x «op» y
とx = y
の両方が許可されている場合にx «op»= y
が許可されるということです。
例: 次のコード内
byte b = 0; char ch = '\0'; int i = 0; b += 1; // OK b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // OK ch += 1; // Error, ch = 1 not permitted ch += (char)1; // OK
各エラーの直感的な理由は、対応する単純な割り当てもエラーだったということです。
end の例
注: これは、複合代入操作がリフトされた演算子をサポートすることも意味します。 複合代入
x «op»= y
はx = x «op» y
またはx = (T)(x «op» y)
として評価されるため、評価の規則では、リフトされた演算子が暗黙的にカバーされます。 end note
12.21.5 イベントの割り当て
a += or -=
演算子の左オペランドがイベント アクセスとして分類されている場合、式は次のように評価されます。
- イベント アクセスのインスタンス式 (存在する場合) が評価されます。
+=
演算子または-=
演算子の右オペランドが評価され、必要に応じて、暗黙的な変換 (§10.2) を使用して左オペランドの型に変換されます。- 前の手順で計算した値で構成される引数リストを使用して、イベントのイベント アクセサーが呼び出されます。 演算子が
+=
された場合は、add アクセサーが呼び出されます。演算子が-=
された場合は、remove アクセサーが呼び出されます。
イベント割り当て式は値を生成しません。 したがって、イベント代入式は、 statement_expression (§13.7) のコンテキストでのみ有効です。
12.22 式
式は、non_assignment_expressionまたはassignmentです。
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.23 定数式
定数式は、コンパイル時に完全に評価される式です。
constant_expression
: expression
;
定数式は、 null
値または次のいずれかの型を持つ必要があります。
sbyte
、byte
、short
、ushort
、int
、uint
、long
、ulong
、char
、float
、double
、decimal
、bool
、string
;- 列挙型。又は
- 参照型の既定値の式 (§12.8.21)。
定数式では、次のコンストラクトのみが許可されます。
- リテラル (
null
リテラルを含む)。 - クラス型と構造体型
const
メンバーへの参照。 - 列挙型のメンバーへの参照。
- ローカル定数への参照。
- 親子化された部分式。それ自体が定数式です。
- 式をキャストします。
checked
およびunchecked
式。nameof
式。- 定義済みの
+
、-
、!
(論理否定)、および単項演算子~
。 - 定義済みの
+
、-
、*
、/
、%
、<<
、>>
、&
、|
、^
、&&
、||
、==
、!=
、<
、>
、<=
、および>=
バイナリ演算子。 ?:
条件演算子。!
null 許容演算子 (§12.8.9)。sizeof
アンマネージ型が定数値を返す §23.6.9 で指定された型の 1 つである場合sizeof
式。- 型が上記の型のいずれかである場合、既定値の式。
定数式では、次の変換を使用できます。
- ID 変換
- 数値変換
- 列挙型の変換
- 定数式の変換
- 変換元が
null
値に評価される定数式である場合、暗黙的および明示的な参照変換。
注:
null
以外の値のボックス化、ボックス化解除、暗黙的な参照変換などのその他の変換は、定数式では使用できません。 end note
例: 次のコード内
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }
ボックス化変換が必要であるため、
i
の初期化はエラーです。null
以外の値からの暗黙的な参照変換が必要であるため、str
の初期化はエラーです。end の例
式が上記の要件を満たすたびに、式はコンパイル時に評価されます。 これは、式が非定数コンストラクトを含む大きな式の部分式である場合でも当てはまります。
定数式のコンパイル時評価では、非定数式の実行時評価と同じ規則が使用されます。ただし、実行時の評価で例外がスローされる場合、コンパイル時の評価によってコンパイル時エラーが発生する点が異なります。
定数式が unchecked
コンテキストに明示的に配置されていない限り、式のコンパイル時の評価中に整数型の算術演算および変換で発生するオーバーフローは、常にコンパイル時エラーを引き起こします (§12.8.20)。
定数式は、以下に示すコンテキストで必要であり、これは constant_expressionを使用して文法で示されます。 これらのコンテキストでは、コンパイル時に式を完全に評価できない場合、コンパイル時エラーが発生します。
- 定数宣言 (§15.4)
- 列挙メンバー宣言 (§19.4)
- パラメーター リストの既定の引数 (§15.6.2)
case
switch
ステートメントのラベル (§13.8.3)。goto case
ステートメント (§13.10.4)- 初期化子を含む配列作成式 (§12.8.17.5) の次元の長さ。
- 属性 (§22)
- constant_pattern (§11.2.3)
暗黙的な定数式変換 (§10.2.11) を使用すると、定数式の値が変換先の型の範囲内にある場合、型 int
の定数式を sbyte
、 byte
、 short
、 ushort
、 uint
、または ulong
に変換できます。
12.24 ブール式
boolean_expressionは、bool
型の結果を生成する式です。次に示すように、直接または特定のコンテキストでoperator true
を適用します。
boolean_expression
: expression
;
if_statementの制御条件式 (§13.8.2)、while_statement (§13.9.2) do_statement (§13.9.3)、またはfor_statement (§13.9.4) はboolean_expressionです。 ?:
演算子 (§12.18) の制御条件式は、boolean_expressionと同じ規則に従いますが、演算子の優先順位の理由から、null_coalescing_expressionとして分類されます。
次のように、bool
型の値を生成できるようにするには、boolean_expressionE
が必要です。
- E が
bool
に暗黙的に変換できる場合は、実行時に暗黙的な変換が適用されます。 - それ以外の場合は、単項演算子のオーバーロード解決 (§12.4.4) を使用して、
E
でのoperator true
の固有の最適な実装を検索し、実行時にその実装が適用されます。 - このような演算子が見つからない場合は、バインド時エラーが発生します。
ECMA C# draft specification