次の方法で共有


ユーザー定義の checked 演算子

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオン号: https://github.com/dotnet/csharplang/issues/4665

概要

C# では、次のユーザー定義演算子の checked バリアントの定義をサポートして、ユーザーが必要に応じてオーバーフロー動作をオプトインまたはオプトアウトできるようにする必要があります。

  • ++および--単項演算子 §12.8.16 および §12.9.6
  • -単項演算子 §12.9.3
  • +-*、および/二項演算子 §12.10
  • 明示的な変換演算子。

モチベーション

ユーザーが型を宣言し、演算子のチェック済みバージョンとチェックされていないバージョンの両方をサポートする方法はありません。 これにより、ライブラリ チームによって公開される提案された generic math インターフェイスを使用するために、さまざまなアルゴリズムを移植することが困難になります。 同様に、これにより、破壊的変更を回避するために、言語が独自のサポートを同時に出荷することなく、 Int128UInt128 などの型を公開できなくなります。

詳細な設計

構文

演算子の文法 (§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は基になる型です。それ以外の場合、T0Tと等しくなります。
  • ユーザー定義演算子のセット ( U) を検索します。 このセットは次で構成されます。
    • 評価コンテキストuncheckedT0のすべての通常のoperator op宣言。
    • checked評価コンテキストでは、ペアワイズマッチングchecked operator宣言を持つ通常の宣言を除き、T0内のすべてのチェックされたoperator op宣言。
  • Uのすべてのoperator op宣言と、そのような演算子のすべてのリフトされた形式について、引数リストAに対して少なくとも 1 つの演算子 (§12.4.6 - Applicable function member) が適用される場合、候補演算子のセットは、T0のすべての該当する演算子で構成されます。
  • それ以外の場合、 T0object場合、候補の演算子のセットは空になります。
  • それ以外の場合、 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 を包含する、またはそれに包含される型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

は、次の箇条書きに置き換えられます。

  • 変換演算子のセット ( U0) を検索します。 このセットは次で構成されます。
    • unchecked評価コンテキストでは、Dのクラスまたは構造体によって宣言された、ユーザー定義の暗黙的または通常の明示的な変換演算子。
    • checked評価コンテキストでは、D内のクラスまたは構造体によって宣言された、ユーザー定義の暗黙的または定期的またはチェックされた明示的な変換演算子が、同じ宣言型内でペアワイズ一致checked operator宣言を持つ通常の明示的な変換演算子を除きます。
  • 適用可能なユーザー定義およびリフトされた変換演算子のセット U を検索します。 このセットは、U0内のユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成され、Sを包含または包含する型から、Tを包含または包含する型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

Checked 演算子と unchecked 演算子 §11.8.20 セクションは、チェック/オフのコンテキストがユーザー定義の明示的な変換の処理に与える影響を反映するように調整されます。

演算子の実装

checked operatorregular 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_Additionop_CheckedAddition、および op_UncheckedAddition をサポートして、破壊的変更の数を制限できます。 これにより、ほとんどのコードでは必要ない可能性が高い複雑さのレイヤーが追加されます。

ECMA-335 の演算子名

また、演算子名は末尾に Checked を使用して、op_UnaryNegationChecked、op_AdditionCheckedop_SubtractionCheckedop_MultiplyCheckedop_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_Additionop_AdditionCheckedの両方を検索します。 該当する関数メンバーのみが checked operatorの場合は、そのメンバーが使用されます。 regular operatorchecked operatorの両方が存在し、同じように適用できる場合は、checked operatorをお勧めします。 regular operatorchecked 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 - 単項演算子オーバーロード解決の最初の行頭文字。

は、次の 2 つの箇条書きに置き換えられます。

  • 現在のチェック/オフコンテキストを照合operator op(x)操作のXによって提供される候補ユーザー定義演算子のセットは、Candidate ユーザー定義演算子のルールを使用して決定されます。
  • 候補のユーザー定義演算子のセットが空でない場合、これは演算の候補演算子のセットになります。 それ以外の場合、操作の X によって提供される候補のユーザー定義演算子のセット operator op(x)、逆のチェック/オフのコンテキストと一致 するかどうかは、 §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。

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

regular operatorが評価コンテキストunchecked一致すると仮定すると、checked operatorは評価コンテキストchecked一致し、checked形式 (たとえば、%) を持たない演算子はどちらのコンテキストにも一致します。§12.4.5 の最初の行頭文字 - 二項演算子オーバーロードの解決:

  • 演算 XY および operator op(x,y) によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、 X によって提供される候補演算子と、 Yによって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。 XY が同じ型の場合、または XY が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。

は、次の 2 つの箇条書きに置き換えられます。

  • 現在のチェック/オフのコンテキストと一致operator op(x,y)、操作のXYによって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、 X によって提供される候補演算子と、 Yによって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。 XY が同じ型の場合、または XY が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。
  • 候補のユーザー定義演算子のセットが空でない場合、これは演算の候補演算子のセットになります。 それ以外の場合は、Xによって指定された候補のユーザー定義演算子のセットと、操作のYoperator op(x,y)、逆のチェック/オフのコンテキストと一致することが決定されます。 セットは、 X によって提供される候補演算子と、 Yによって提供される候補演算子の和集合で構成され、それぞれが §12.4.6 - Candidate ユーザー定義演算子の規則を使用して決定されます。 XY が同じ型の場合、または XY が共通の基本型から派生している場合、共有候補演算子は結合セット内に 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 を包含する、またはそれに包含される型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

は、次の箇条書きに置き換えられます。

  • 現在のチェック/オフのコンテキスト (U0) と一致する、適用可能なユーザー定義およびリフトされた明示的な変換演算子のセットを検索します。 このセットは、現在のチェック/オフのコンテキストに一致するD内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、Sを包含または包含する型からTを包含または包含する型に変換します。
  • オン/オフの逆のコンテキストと一致する、適用可能なユーザー定義およびリフトされた明示的な変換演算子のセットを検索U1U0が空でない場合、U1は空です。 それ以外の場合、このセットは、逆のチェック/オフのコンテキストに一致するDのクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、Sを包含または包含する型から、Tを包含または包含する型に変換します。
  • 適用可能なユーザー定義およびリフトされた変換演算子のセット U を検索します。 このセットは、 U0U1、および D 内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的な変換演算子で構成されます。この演算子は、 S を包含または包含する型から、 Tを包含または包含する型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

さらに、候補のユーザー定義演算子のセットを構築するもう 1 つの方法

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

セクション §12.4.4 の最初の行頭文字は、次のように調整されます (追加は太字です)。

  • 操作operator op(x)Xによって提供される候補ユーザー定義演算子のセットは、後述の「ユーザー定義演算子候補」セクションのルールを使用して決定されます。 チェックされたフォームに少なくとも 1 つの演算子がセットに含まれている場合、通常の形式のすべての演算子がセットから削除されます。

セクション §12.8.20 は、単項演算子のオーバーロード解決に対するチェック/オフコンテキストの効果を反映するように調整されます。

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

セクション §12.4.5 の最初の行頭文字は、次のように調整されます (追加は太字です)。

  • 演算 XY および operator op(x,y) によって提供される候補ユーザー定義演算子のセットが決定されます。 セットは、 X によって提供される候補演算子と、 Yによって提供される候補演算子の和集合で構成されます。各演算子は、以下の「ユーザー定義演算子候補」セクションの規則を使用して決定されます。 XY が同じ型の場合、または XY が共通の基本型から派生している場合、共有候補演算子は結合セット内に 1 回だけ出現します。 チェックされたフォームに少なくとも 1 つの演算子がセットに含まれている場合、通常の形式のすべての演算子がセットから削除されます。

Checked 演算子と unchecked 演算子 §12.8.20 セクションは、チェック/オフのコンテキストが二項演算子のオーバーロード解決に与える影響を反映するように調整されます。

ユーザー定義演算子候補

§12.4.6 - ユーザー定義演算子候補セクションは、次のように調整されます (追加は太字です)。

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

  • T0 型を決定します。 Tが null 許容型の場合、T0は基になる型です。それ以外の場合、T0Tと等しくなります。
  • checked評価コンテキスト内のチェック済みおよび標準形式のすべてのoperator op宣言について、T0およびそのような演算子のすべてのリフトされた形式のunchecked評価コンテキスト内の通常の形式でのみ、引数リストAに対して少なくとも 1 つの演算子 (§12.6.4.2) が適用できる場合、候補演算子のセットは、T0のすべての該当する演算子で構成されます。
  • それ以外の場合、 T0object場合、候補の演算子のセットは空になります。
  • それ以外の場合、 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 を包含する、またはそれに包含される型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

は、次の箇条書きに置き換えられます。

  • 適用可能なユーザー定義およびリフトされた明示的な変換演算子のセット ( U0) を検索します。 このセットは、Dのクラスまたは構造体によって宣言されたユーザー定義およびリフトされた明示的な変換演算子で構成され、checked評価コンテキスト内のチェックフォームと標準フォームでのみ、unchecked評価コンテキスト内の通常の形式でSを包含または包含する型から、Tを包含または包含する型に変換します。
  • U0チェック フォームに少なくとも 1 つの演算子が含まれている場合、通常の形式のすべての演算子がセットから削除されます。
  • 適用可能なユーザー定義およびリフトされた変換演算子のセット U を検索します。 このセットは、 U0の演算子と、 D 内のクラスまたは構造体によって宣言されたユーザー定義およびリフトされた暗黙的な変換演算子で構成されます。この演算子は、 S を包含または包含する型から、 Tを包含または包含する型に変換します。 If U が空である場合、その変換は未定義であり、コンパイル時エラーが発生します。

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#checked-implicit-conversions

デザインに関する会議

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