注
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオン号: https://github.com/dotnet/csharplang/issues/4665
概要
C# では、次のユーザー定義演算子の checked
バリアントの定義をサポートして、ユーザーが必要に応じてオーバーフロー動作をオプトインまたはオプトアウトできるようにする必要があります。
モチベーション
ユーザーが型を宣言し、演算子のチェック済みバージョンとチェックされていないバージョンの両方をサポートする方法はありません。 これにより、ライブラリ チームによって公開される提案された generic math
インターフェイスを使用するために、さまざまなアルゴリズムを移植することが困難になります。 同様に、これにより、破壊的変更を回避するために、言語が独自のサポートを同時に出荷することなく、 Int128
や UInt128
などの型を公開できなくなります。
詳細な設計
構文
演算子の文法 (§15.10) は、演算子トークンの直前の operator
キーワードの後checked
キーワードを許可するように調整されます。
overloadable_unary_operator
: '+' | 'checked'? '-' | '!' | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
;
overloadable_binary_operator
: 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' type identifier ')'
| 'explicit' 'operator' 'checked'? type '(' type identifier ')'
;
例えば次が挙げられます。
public static T operator checked ++(T x) {...}
public static T operator checked --(T x) {...}
public static T operator checked -(T x) {...}
public static T operator checked +(T lhs, T rhs) {...}
public static T operator checked -(T lhs, T rhs) {...}
public static T operator checked *(T lhs, T rhs) {...}
public static T operator checked /(T lhs, T rhs) {...}
public static explicit operator checked U(T x) {...}
public static T I1.operator checked ++(T x) {...}
public static T I1.operator checked --(T x) {...}
public static T I1.operator checked -(T x) {...}
public static T I1.operator checked +(T lhs, T rhs) {...}
public static T I1.operator checked -(T lhs, T rhs) {...}
public static T I1.operator checked *(T lhs, T rhs) {...}
public static T I1.operator checked /(T lhs, T rhs) {...}
public static explicit I1.operator checked U(T x) {...}
以下で簡潔にするために、 checked
キーワードを持つ演算子は checked operator
と呼ばれ、演算子を含まない演算子は regular operator
と呼ばれます。 これらの用語は、 checked
フォームを持たない演算子には適用されません。
意味論
操作の結果が大きすぎて変換先の型で表すには、ユーザー定義の checked operator
が例外をスローすることが予想されます。 大きすぎるとはどういう意味ですか? 実際には宛先の種類の性質に依存し、言語によって規定されていません。 通常、スローされる例外は System.OverflowException
ですが、言語にはこれに関する特定の要件はありません。
操作の結果が大きすぎて変換先の型で表現できない場合、ユーザー定義の regular operator
は例外をスローしないことが予想されます。 代わりに、切り捨てられた結果を表すインスタンスを返す必要があります。 大きすぎると切り捨てられるとはどういう意味ですか? 実際には変換先の型の性質に依存し、言語によって規定されていません。
checked
フォームがサポートされているすべての既存のユーザー定義演算子は、regular operators
のカテゴリに分類されます。 それらの多くは上記のセマンティクスに従っていない可能性が高いと理解されていますが、セマンティック分析の目的上、コンパイラはそれらがそうであると想定します。
内のチェックされたコンテキストとチェックされていないコンテキスト checked operator
checked operator
の本文内のチェック/オフのコンテキストは、checked
キーワードの存在の影響を受けません。 つまり、コンテキストは演算子宣言の先頭と同じです。 開発者は、アルゴリズムの一部が既定のコンテキストに依存できない場合、コンテキストを明示的に切り替える必要があります。
メタデータ内の名前
ECMA-335 のセクション "I.10.3.1 単項演算子" は、チェックされた++
、--
、-
単項演算子を実装するメソッドの名前として、op_CheckedIncrement、op_CheckedDecrement、op_CheckedUnaryNegationを含むように調整されます。
ECMA-335 のセクション 「I.10.3.2 二項演算子」は、チェックされた+
、-
、*
、および/
二項演算子を実装するメソッドの名前として、op_CheckedAddition、op_CheckedSubtraction、op_CheckedMultiply、op_CheckedDivisionを含むように調整されます。
ECMA-335 のセクション "I.10.3.3 変換演算子" は、チェックされた明示的な変換演算子を実装するメソッドの名前として op_CheckedExplicit を含むように調整されます。
単項演算子
単項 checked operators
§15.10.2 の規則に従います。
また、 checked operator
宣言には、 regular operator
のペアごとの宣言が必要です (戻り値の型も一致する必要があります)。 それ以外の場合は、コンパイル時エラーが発生します。
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked -(Int128 lhs);
public static Int128 operator -(Int128 lhs);
// This is fine, only a regular operator is defined
public static Int128 operator --(Int128 lhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked ++(Int128 lhs);
}
バイナリ演算子
バイナリ checked operators
§15.10.3 の規則に従います。
また、 checked operator
宣言には、 regular operator
のペアごとの宣言が必要です (戻り値の型も一致する必要があります)。 それ以外の場合は、コンパイル時エラーが発生します。
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
// This is fine, only a regular operator is defined
public static Int128 operator -(Int128 lhs, Int128 rhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
ユーザー定義演算子候補
候補ユーザー演算子 (§12.4.6) セクションは、次のように調整されます (追加/変更は太字で表示されます)。
型T
と操作operator op(A)
op
がオーバーロード可能な演算子であり、A
が引数リストである場合、operator op(A)
に対してT
によって提供される候補ユーザー定義演算子のセットは次のように決定されます。
-
T0
型を決定します。T
が null 許容型の場合、T0
は基になる型です。それ以外の場合、T0
はT
と等しくなります。 - ユーザー定義演算子のセット (
U
) を検索します。 このセットは次で構成されます。-
評価コンテキスト
unchecked
、T0
のすべての通常のoperator op
宣言。 -
checked
評価コンテキストでは、ペアワイズマッチングchecked operator
宣言を持つ通常の宣言を除き、T0
内のすべてのチェックされたoperator op
宣言。
-
評価コンテキスト
-
U
のすべてのoperator op
宣言と、そのような演算子のすべてのリフトされた形式について、引数リストA
に対して少なくとも 1 つの演算子 (§12.4.6 - Applicable function member) が適用される場合、候補演算子のセットは、T0
のすべての該当する演算子で構成されます。 - それ以外の場合、
T0
がobject
場合、候補の演算子のセットは空になります。 - それ以外の場合、
T0
によって提供される候補演算子のセットは、T0
の直接基底クラスによって提供される候補演算子のセット、またはT0
が型パラメーターの場合はT0
の有効な基底クラスです。
インターフェイス https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfacesの候補演算子のセットを決定する際に、同様のルールが適用されます。
セクション §12.8.20 は、単項演算子と二項演算子のオーバーロード解決に対するチェック/オフコンテキストの影響を反映するように調整されます。
例 #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r6 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_Division` - it is a better match than `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
例 #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
#3 の例:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
変換演算子
変換checked operators
§15.10.4 の規則に従います。
ただし、 checked operator
宣言には、 regular operator
のペアごとの宣言が必要です。 それ以外の場合は、コンパイル時エラーが発生します。
次の段落
変換演算子のシグネチャは、ソース型とターゲット型で構成されます。 (これは、戻り値の型が署名に参加するメンバーの唯一の形式です)。変換演算子の暗黙的または明示的な分類は、演算子のシグネチャの一部ではありません。 したがって、クラスまたは構造体は、同じソース型とターゲット型を持つ暗黙的な変換演算子と明示的な変換演算子の両方を宣言することはできません。
は、型が同じソース型とターゲット型を使用して、チェックされた通常の形式の明示的な変換を宣言できるように調整されます。 型は、同じソース型とターゲット型で暗黙的な変換演算子とチェックされた明示的な変換演算子の両方を宣言することはできません。
ユーザー定義の明示的な変換の処理
§10.5.5 の 3 番目の行頭文字:
- 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、D
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成され、S
を包含する、またはそれに包含される型から、T
を包含する、またはそれに包含される型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
は、次の箇条書きに置き換えられます。
- 変換演算子のセット (
U0
) を検索します。 このセットは次で構成されます。-
unchecked
評価コンテキストでは、D
のクラスまたは構造体によって宣言された、ユーザー定義の暗黙的または通常の明示的な変換演算子。 -
checked
評価コンテキストでは、D
内のクラスまたは構造体によって宣言された、ユーザー定義の暗黙的または定期的またはチェックされた明示的な変換演算子が、同じ宣言型内でペアワイズ一致checked operator
宣言を持つ通常の明示的な変換演算子を除きます。
-
- 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、U0
内のユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成され、S
を包含または包含する型から、T
を包含または包含する型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
Checked 演算子と unchecked 演算子 §11.8.20 セクションは、チェック/オフのコンテキストがユーザー定義の明示的な変換の処理に与える影響を反映するように調整されます。
演算子の実装
checked operator
はregular operator
を実装しません。その逆も同様です。
Linq 式ツリー
Checked operators
は Linq 式ツリーでサポートされます。
UnaryExpression
/
BinaryExpression
ノードは、対応するMethodInfo
で作成されます。
次のファクトリ メソッドが使用されます。
public static UnaryExpression NegateChecked (Expression expression, MethodInfo? method);
public static BinaryExpression AddChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression SubtractChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression MultiplyChecked (Expression left, Expression right, MethodInfo? method);
public static UnaryExpression ConvertChecked (Expression expression, Type type, MethodInfo? method);
C# は式ツリーでの代入をサポートしていないため、チェックされたインクリメント/デクリメントもサポートされないことに注意してください。
チェックされた除算のファクトリ メソッドはありません。 Linq 式ツリーのチェックされた除算に関して、未解決の質問があります。
動的
CoreCLR での動的呼び出しでチェック演算子のサポートを追加するコストを調査し、コストが高すぎなければ実装を追求します。 これは https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.mdからの引用です。
デメリット
これにより、言語の複雑さが増し、ユーザーは型により多くの種類の破壊的変更を導入できるようになります。
選択肢
ライブラリが公開する予定の汎用数学インターフェイスでは、名前付きメソッド ( AddChecked
など) を公開できます。 主な欠点は、読み取り/保守が容易でなく、演算子に関する言語優先順位規則の利点が得られないということです。
このセクションでは、説明されている代替手段の一覧を示しますが、実装されていません
checked
キーワードの配置
または、 checked
キーワードを、 operator
キーワードの直前の場所に移動することもできます。
public static T checked operator ++(T x) {...}
public static T checked operator --(T x) {...}
public static T checked operator -(T x) {...}
public static T checked operator +(T lhs, T rhs) {...}
public static T checked operator -(T lhs, T rhs) {...}
public static T checked operator *(T lhs, T rhs) {...}
public static T checked operator /(T lhs, T rhs) {...}
public static explicit checked operator U(T x) {...}
public static T checked I1.operator ++(T x) {...}
public static T checked I1.operator --(T x) {...}
public static T checked I1.operator -(T x) {...}
public static T checked I1.operator +(T lhs, T rhs) {...}
public static T checked I1.operator -(T lhs, T rhs) {...}
public static T checked I1.operator *(T lhs, T rhs) {...}
public static T checked I1.operator /(T lhs, T rhs) {...}
public static explicit checked I1.operator U(T x) {...}
または、演算子修飾子のセットに移動することもできます。
operator_modifier
: 'public'
| 'static'
| 'extern'
| 'checked'
| operator_modifier_unsafe
;
public static checked T operator ++(T x) {...}
public static checked T operator --(T x) {...}
public static checked T operator -(T x) {...}
public static checked T operator +(T lhs, T rhs) {...}
public static checked T operator -(T lhs, T rhs) {...}
public static checked T operator *(T lhs, T rhs) {...}
public static checked T operator /(T lhs, T rhs) {...}
public static checked explicit operator U(T x) {...}
public static checked T I1.operator ++(T x) {...}
public static checked T I1.operator --(T x) {...}
public static checked T I1.operator -(T x) {...}
public static checked T I1.operator +(T lhs, T rhs) {...}
public static checked T I1.operator -(T lhs, T rhs) {...}
public static checked T I1.operator *(T lhs, T rhs) {...}
public static checked T I1.operator /(T lhs, T rhs) {...}
public static checked explicit I1.operator U(T x) {...}
unchecked
キーワード
checked
キーワードと同じ位置にあるキーワードunchecked
サポートするための提案があり、次の意味が考えられます。
- 演算子の規則的な性質を明示的に反映する、または
- おそらく、
unchecked
コンテキストで使用されるはずの演算子の個別のフレーバーを指定します。 この言語では、op_Addition
、op_CheckedAddition
、およびop_UncheckedAddition
をサポートして、破壊的変更の数を制限できます。 これにより、ほとんどのコードでは必要ない可能性が高い複雑さのレイヤーが追加されます。
ECMA-335 の演算子名
また、演算子名は、末尾に Checked を使用して、op_UnaryNegationChecked、op_AdditionChecked、op_SubtractionChecked、op_MultiplyChecked、op_DivisionCheckedにすることができます。 ただし、演算子ワードで名前を終了するパターンが既に確立されているようです。 たとえば、op_RightShiftUnsigned演算子ではなく、op_UnsignedRightShift演算子があります。
Checked operators
は、 unchecked
コンテキストでは適用できません
コンパイラは、メンバー検索を実行して、 unchecked
コンテキスト内で候補のユーザー定義演算子を検索するときに、 checked operators
を無視できます。
checked operator
のみを定義するメタデータが検出された場合は、コンパイル エラーが発生します。
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
checked
コンテキストでのより複雑な演算子の参照とオーバーロードの解決規則
コンパイラは、メンバー参照を実行して、 checked
コンテキスト内で候補のユーザー定義演算子を検索する場合、 Checked
で終わる適用可能な演算子も考慮します。 つまり、コンパイラがバイナリ加算演算子の適用可能な関数メンバーを見つけようとしていた場合は、 op_Addition
と op_AdditionChecked
の両方を検索します。 該当する関数メンバーのみが checked operator
の場合は、そのメンバーが使用されます。
regular operator
とchecked operator
の両方が存在し、同じように適用できる場合は、checked operator
をお勧めします。
regular operator
とchecked operator
の両方が存在するが、regular operator
が完全に一致しているのにchecked operator
が存在しない場合、コンパイラはregular operator
を優先します。
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
}
public static void Multiply(Int128 lhs, byte rhs)
{
// Resolves to `op_Multiply` even though `op_CheckedMultiply` is also applicable
Int128 r4 = checked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, int rhs);
public static Int128 operator *(Int128 lhs, byte rhs);
}
さらに、候補のユーザー定義演算子のセットを構築する別の方法
単項演算子のオーバーロードの解決
regular operator
が評価コンテキストunchecked
一致すると仮定すると、checked operator
は評価コンテキストchecked
一致し、checked
形式 (たとえば、+
) を持たない演算子はどちらのコンテキストにも一致します。§12.4.4 - 単項演算子オーバーロード解決の最初の行頭文字。
- 操作
operator op(x)
のX
によって提供される候補ユーザー定義演算子のセットは、§12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。
は、次の 2 つの箇条書きに置き換えられます。
-
現在のチェック/オフコンテキストを照合
operator op(x)
操作のX
によって提供される候補ユーザー定義演算子のセットは、Candidate ユーザー定義演算子のルールを使用して決定されます。 - 候補のユーザー定義演算子のセットが空でない場合、これは演算の候補演算子のセットになります。 それ以外の場合、操作の
X
によって提供される候補のユーザー定義演算子のセットoperator op(x)
、逆のチェック/オフのコンテキストと一致 するかどうかは、 §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。
二項演算子オーバーロードの解決
regular operator
が評価コンテキストunchecked
一致すると仮定すると、checked operator
は評価コンテキストchecked
一致し、checked
形式 (たとえば、%
) を持たない演算子はどちらのコンテキストにも一致します。§12.4.5 の最初の行頭文字 - 二項演算子オーバーロードの解決:
- 演算
X
のY
およびoperator op(x,y)
によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、X
によって提供される候補演算子と、Y
によって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。X
とY
が同じ型の場合、またはX
とY
が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。
は、次の 2 つの箇条書きに置き換えられます。
-
現在のチェック/オフのコンテキストと一致
operator op(x,y)
、操作のX
とY
によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、X
によって提供される候補演算子と、Y
によって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。X
とY
が同じ型の場合、またはX
とY
が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。 - 候補のユーザー定義演算子のセットが空でない場合、これは演算の候補演算子のセットになります。 それ以外の場合は、
X
によって指定された候補のユーザー定義演算子のセットと、操作のY
operator op(x,y)
、逆のチェック/オフのコンテキストと一致することが決定されます。 セットは、X
によって提供される候補演算子と、Y
によって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。X
とY
が同じ型の場合、またはX
とY
が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。
例 #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
例 #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
#3 の例:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
例 4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
例 5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
ユーザー定義の明示的な変換の処理
regular operator
が評価コンテキストunchecked
一致し、checked operator
が評価コンテキストchecked
一致すると仮定すると、§10.5.3 ユーザー定義変換の評価の 3 番目の行頭文字は次のようになります。
- 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、D
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成され、S
を包含する、またはそれに包含される型から、T
を包含する、またはそれに包含される型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
は、次の箇条書きに置き換えられます。
- 現在のチェック/オフのコンテキスト (
U0
) と一致する、適用可能なユーザー定義およびリフトされた明示的な変換演算子のセットを検索します。 このセットは、現在のチェック/オフのコンテキストに一致するD
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、S
を包含または包含する型からT
を包含または包含する型に変換します。 -
オン/オフの逆のコンテキストと一致する、適用可能なユーザー定義およびリフトされた明示的な変換演算子のセットを検索
U1
。U0
が空でない場合、U1
は空です。 それ以外の場合、このセットは、逆のチェック/オフのコンテキストに一致するD
のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、S
を包含または包含する型から、T
を包含または包含する型に変換します。 - 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、U0
、U1
、およびD
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的な変換演算子で構成されます。この演算子は、S
を包含または包含する型から、T
を包含または包含する型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
さらに、候補のユーザー定義演算子のセットを構築するもう 1 つの方法
単項演算子のオーバーロードの解決
セクション §12.4.4 の最初の行頭文字は、次のように調整されます (追加は太字です)。
- 操作
operator op(x)
のX
によって提供される候補ユーザー定義演算子のセットは、後述の「ユーザー定義演算子候補」セクションのルールを使用して決定されます。 チェックされたフォームに少なくとも 1 つの演算子がセットに含まれている場合、通常の形式のすべての演算子がセットから削除されます。
セクション §12.8.20 は、単項演算子のオーバーロード解決に対するチェック/オフコンテキストの効果を反映するように調整されます。
二項演算子オーバーロードの解決
セクション §12.4.5 の最初の行頭文字は、次のように調整されます (追加は太字です)。
- 演算
X
のY
およびoperator op(x,y)
によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、X
によって提供される候補演算子と、Y
によって提供される候補演算子の和集合で構成されます。各演算子は、以下の「ユーザー定義演算子候補」セクションの規則を使用して決定されます。X
とY
が同じ型の場合、またはX
とY
が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。 チェックされたフォームに少なくとも 1 つの演算子がセットに含まれている場合、通常の形式のすべての演算子がセットから削除されます。
Checked 演算子と unchecked 演算子 §12.8.20 セクションは、チェック/オフのコンテキストが二項演算子のオーバーロード解決に与える影響を反映するように調整されます。
ユーザー定義演算子候補
§12.4.6 - ユーザー定義演算子候補セクションは、次のように調整されます (追加は太字です)。
型T
と操作operator op(A)
op
がオーバーロード可能な演算子であり、A
が引数リストである場合、operator op(A)
に対してT
によって提供される候補ユーザー定義演算子のセットは次のように決定されます。
-
T0
型を決定します。T
が null 許容型の場合、T0
は基になる型です。それ以外の場合、T0
はT
と等しくなります。 -
checked
評価コンテキスト内のチェック済みおよび標準形式のすべてのoperator op
宣言について、T0
およびそのような演算子のすべてのリフトされた形式のunchecked
評価コンテキスト内の通常の形式でのみ、引数リストA
に対して少なくとも 1 つの演算子 (§12.6.4.2) が適用できる場合、候補演算子のセットは、T0
のすべての該当する演算子で構成されます。 - それ以外の場合、
T0
がobject
場合、候補の演算子のセットは空になります。 - それ以外の場合、
T0
によって提供される候補演算子のセットは、T0
の直接基底クラスによって提供される候補演算子のセット、またはT0
が型パラメーターの場合はT0
の有効な基底クラスです。
同様のフィルター処理が適用され、インターフェイス https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfacesの候補演算子のセットが決定されます。
§12.8.20 セクションは、単項演算子と二項演算子のオーバーロード解決に対するチェック/オフコンテキストの影響を反映するように調整されます。
例 #1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
例 #2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
#3 の例:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
例 4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
例 5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
ユーザー定義の明示的な変換の処理
§10.5.5 の 3 番目の行頭文字:
- 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、D
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成され、S
を包含する、またはそれに包含される型から、T
を包含する、またはそれに包含される型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
は、次の箇条書きに置き換えられます。
- 適用可能なユーザー定義およびリフトされた明示的な変換演算子のセット (
U0
) を検索します。 このセットは、D
のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、checked
評価コンテキスト内のチェックフォームと標準フォームでのみ、unchecked
評価コンテキスト内の通常の形式で、S
を包含または包含する型から、T
を包含または包含する型に変換します。 -
U0
チェック フォームに少なくとも 1 つの演算子が含まれている場合、通常の形式のすべての演算子がセットから削除されます。 - 適用可能なユーザー定義およびリフトされた変換演算子のセット
U
を検索します。 このセットは、U0
の演算子と、D
内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的な変換演算子で構成されます。この演算子は、S
を包含または包含する型から、T
を包含または包含する型に変換します。 IfU
が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。
Checked 演算子と unchecked 演算子 §12.8.20 セクションは、チェック/オフのコンテキストがユーザー定義の明示的な変換の処理に与える影響を反映するように調整されます。
内のチェックされたコンテキストとチェックされていないコンテキスト checked operator
コンパイラは、 checked operator
の既定のコンテキストをチェック済みとして扱う場合があります。 開発者は、アルゴリズムの一部がchecked context
に参加してはならない場合は、unchecked
を明示的に使用する必要があります。 ただし、 checked
/unchecked
トークンを演算子の修飾子として許可し始め、本文内でコンテキストを設定し始めると、これは将来うまく機能しない可能性があります。 修飾子とキーワードは互いに矛盾する可能性があります。 また、 regular operator
に対して同じ (既定のコンテキストをオフに扱う) ことはできなくなります。これは破壊的変更になるためです。
未解決の質問
言語でメソッドの checked
修飾子と unchecked
修飾子 ( static checked void M()
など) を許可する必要がありますか?
これにより、必要なメソッドの入れ子レベルを削除できます。
Linq 式ツリーのチェックされた除算
チェックされた除算ノードを作成するファクトリ メソッドはなく、 ExpressionType.DivideChecked
メンバーもありません。
次のファクトリ メソッドを使用して、op_CheckedDivision
メソッドを指すMethodInfo
を持つ通常の除算ノードを作成することもできます。
コンシューマーは、コンテキストを推論するために名前を確認する必要があります。
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
§12.8.20 セクションでは、チェック/オフの評価コンテキストの影響を受ける演算子の 1 つとして演算子/
一覧表示されますが、IL には、チェックされた除算を実行するための特別な操作コードはありません。
コンパイラは、現在、コンテキストのリダッドレスのファクトリ メソッドを常に使用します。
建議: チェックされたユーザー定義のプロビジョニング解除は、Linq 式ツリーではサポートされません。
(解決済み)暗黙的なチェックされた変換演算子をサポートする必要がありますか?
一般に、暗黙的な変換演算子はスローされません。
建議: いいえ。
デザインに関する会議
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md
C# feature specifications