共用方式為


已檢查的使用者定義運算符

注意

本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。

功能規格與已完成實作之間可能有一些差異。 這些差異是在相關的 語言設計會議(LDM)記錄中捕捉到的。

您可以在關於 規格的文章中深入了解將功能小規範整合到 C# 語言標準的過程

總結

C# 應該支援定義下列使用者定義運算子的 checked 變體,以便使用者能夠選擇加入或退出溢位行為:

動機

用戶無法宣告類型,而且同時支援已核取和未核取的運算符版本。 這很難移植各種演算法,以使用連結庫小組所公開的建議 generic math 介面。 同樣地,這樣就無法公開類型,例如 Int128UInt128,而不需要語言同時提供自身的支援,以避免重大變更。

詳細設計

語法

運算子的文法(§15.10)將會進行調整,以允許在運算子標記前,checked 關鍵詞後緊接著使用 operator 關鍵詞:

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_CheckedIncrementop_CheckedDecrementop_CheckedUnaryNegation 作為實作已檢查 ++--- 一元運算符的方法名稱。

ECMA-335 的「I.10.3.2 二元運算符」區段將調整為包含 op_CheckedAdditionop_CheckedSubtractionop_CheckedMultiplyop_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 是參數列表,則由 Toperator op(A) 所提供的候選使用者定義運算符集合如下:

  • 判斷類型 T0。 如果 T 是可為 Null 的類型,T0 是其基礎類型,否則 T0 等於 T
  • 尋找使用者定義的運算子,U。 此集合包含:
    • unchecked 評估內容中,operator op中的所有一般 T0 宣告。
    • checked 評估內容中,operator op 所有已檢查的和一般 T0 宣告,除了具有成對匹配 checked operator 宣告的一般宣告之外。
  • 對於 operator op 中的所有 U 宣告以及這些運算子的所有提升形式,如果就自變數清單 而言,至少有一個運算子適用(A),則候選運算子集合包含 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

  • 尋找一組適用的使用者定義和提升轉換運算子,U。 這個集合包含由類別或結構在 D 中宣告的使用者定義和提升的隱含或明確轉換運算符,這些運算符將從涵蓋或被 S 涵蓋的類型轉換成涵蓋或被 T涵蓋的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

將會替換成下列項目符號:

  • 尋找轉換運算子集,U0。 此集合包含:
    • unchecked 評估內容中,D中類別或結構所宣告的使用者定義隱含或一般明確轉換運算符。
    • checked 評估內容中,D 類別或結構所宣告的使用者定義隱含或一般/檢查明確轉換運算符,除了在相同宣告類型內具有配對比對 checked operator 宣告的一般明確轉換運算符除外。
  • 尋找一組適用的使用者定義和提升轉換運算子,U。 此集合包含使用者定義和提升的隱含或明確轉換運算符,U0 中的隱含或明確轉換運算符,這些運算符會從包含或包含 S 的類型轉換成包含或包含 T的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

將會調整 checked 和 unchecked 運算子 §11.8.20 區段,以反映已核取/未核取的環境對處理使用者定義明確轉換的影響。

實作運算子

checked operator 不會實作 regular operator,反之亦然。

Linq 運算式樹狀架構

會在 Linq 表達樹中支援 Checked operators。 將會使用對應的 UnaryExpression建立 /BinaryExpressionMethodInfo 節點。 將會使用下列工廠方法:

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# 不支援在表達式樹中進行指派,因此也不支援檢查的加減功能。

沒有檢查除法的 Factory 方法。 有一個關於此問題的未解答問題 - 在 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 關鍵詞

有建議支援與 unchecked 關鍵詞位於相同位置的 checked 關鍵詞,並具有下列可能的意義:

  • 僅需明確反映運算子之規律性,或
  • 也許是為了指定運算子在 unchecked 環境中使用的特殊版本。 語言可支援 op_Additionop_CheckedAdditionop_UncheckedAddition,以協助限制破壞性變更的數目。 這會增加在大部分程序代碼中可能不需要的另一層複雜度。

ECMA-335 中的運算子名稱

或者,運算符名稱可以是 op_UnaryNegationCheckedop_AdditionCheckedop_SubtractionCheckedop_MultiplyCheckedop_DivisionChecked,結尾則為 Checked。 不過,看起來已經有一個將名稱以運算符結尾的模式已經建立。 例如,有一個 op_UnsignedRightShift 運算符,而不是 op_RightShiftUnsigned 運算符。

Checked operatorsunchecked 上下文中不適用

編譯程式在執行成員查閱以在 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 operatorchecked 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 - 一元運算符多載解析的第一個項目符號中:

將取代為下列兩個項目:

  • 提供的候選使用者定義運算符集,是針對操作 並符合當前的核取/未核取狀態內容,使用的候選使用者定義運算子的規則來決定的。
  • 如果候選使用者定義運算符集合不是空的,則這會成為作業的候選運算元集合。 否則,由 X 所提供的針對作業 operator op(x)的候選使用者定義運算符集合,用於匹配相反的核取/未核取上下文,將依據 第12.4.6節的規則「候選使用者定義運算符」來決定。

二元運算子多載解析

假設 regular operator 符合 unchecked 評估內容,checked operator 符合 checked 評估內容,並且一個沒有 checked 格式的運算符(例如,%)匹配任一內容,則在 §12.4.5 - 二元運算符重載解析中的第一項:

  • 已確定由 XY 為作業 operator op(x,y) 所提供的一組候選使用者定義運算子。 此集合由 X 提供的候選運算符和由 Y提供的候選運算符所組成的聯集,每個運算子都是根據 §12.4.6 - 候選使用者定義運算符的規則來決定。 如果 XY 是相同的類型,或如果 XY 衍生自一般基底類型,則共用候選運算符只會發生在合併集一次。

將取代為下列兩個項目:

  • 在目前核取/未核取內容 的條件下, 所提供用於操作 的候選使用者定義運算元集合已被確定。 此集合由 X 提供的候選運算符和由 Y提供的候選運算符所組成的聯集,每個運算子都是根據 §12.4.6 - 候選使用者定義運算符的規則來決定。 如果 XY 是相同的類型,或如果 XY 衍生自一般基底類型,則共用候選運算符只會發生在合併集一次。
  • 如果候選使用者定義運算符集合不是空的,則這會成為作業的候選運算元集合。 否則,會決定 XY 所提供的候選使用者定義運算符集,這些運算符用於作業 operator op(x,y),並符合相反的核取/未核取上下文。 此集合由 X 提供的候選運算符和由 Y提供的候選運算符所組成的聯集,每個運算子都是根據 §12.4.6 - 候選使用者定義運算符的規則來決定。 如果 XY 是相同的類型,或如果 XY 衍生自一般基底類型,則共用候選運算符只會發生在合併集一次。
範例 #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 評估內容,則 第三條使用者定義轉換的評估:

  • 尋找一組適用的使用者定義和提升轉換運算子,U。 這個集合包含由類別或結構在 D 中宣告的使用者定義和提升的隱含或明確轉換運算符,這些運算符將從涵蓋或被 S 涵蓋的類型轉換成涵蓋或被 T涵蓋的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

將會替換成下列項目符號:

  • 尋找一組適用的使用者定義和隨即解除的明確轉換運算符,符合目前核取/未核取的內容U0。 此集合是由 D 中類別或結構所宣告的使用者定義和解除明確轉換運算子所組成,符合目前核取/未核取的內容,並從包含或包含 S 的類型轉換成包含或包含 T所包含的類型。
  • 尋找一組適用的使用者定義和提升的明確轉換運算子,能夠與相反的已檢查/未檢查上下文U1相匹配。 如果 U0 不是空的,U1 是空的。 否則,這個集合是由 D 中類別或結構所宣告的使用者定義和提升明確轉換運算子所組成,符合相反的核取/未核取內容,並且從包含或包含 S 的類型轉換成由 T所包含或包含的類型。
  • 尋找一組適用的使用者定義和提升轉換運算子,U。 此集合包含 U0U1運算符,以及由 D 中的類別或結構所宣告的使用者定義及提升的隱式轉換運算符,這些運算符會將包含或被 S 包含的類型轉換為包含或被 T包含的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

此外,還有另外一種建置候選使用者定義運算符集的方法。

一元運算子重載解析

章節 §12.4.4 中的第一個項目符號將會調整如下(新增部分以粗體顯示)。

  • X 提供的操作 operator op(x) 的候選使用者定義運算子集合,依照下列「候選使用者定義運算子」一節中的規則來確定。 如果集合中至少包含一個已勾選格式的運算符,則會移除集合中所有非勾選格式的運算符。

區段 §12.8.20 將會調整,以反映勾選/未勾選內容對一元運算子多載解析的影響。

二元運算子多載解析

段落 §12.4.5 的第一個項目符號將會調整如下(新增部分為粗體)。

  • 已確定由 XY 為作業 operator op(x,y) 所提供的一組候選使用者定義運算子。 此集合包含 X 所提供的候選運算符聯集,以及由 Y提供的候選運算符,每個運算元都是使用下列「候選使用者定義運算元」一節的規則來決定。 如果 XY 是相同的類型,或如果 XY 衍生自一般基底類型,則共用候選運算符只會發生在合併集一次。 如果集合中至少包含一個已勾選格式的運算符,則會移除集合中所有非勾選格式的運算符。

checked 和 unchecked 運算子 §12.8.20 區段將會調整,來反映 checked/unchecked 上下文對二元運算符重載解析的影響。

候選使用者定義運算符

§12.4.6 - 候選使用者定義運算子 區段將會調整如下(新增部分將以粗體顯示)。

假設有一個類型 T 和一個操作 operator op(A),其中 op 是一個可重載運算符,而 A 是參數列表,則由 Toperator op(A) 所提供的候選使用者定義運算符集合如下:

  • 判斷類型 T0。 如果 T 是可為 Null 的類型,T0 是其基礎類型,否則 T0 等於 T
  • 針對 評估內容中的所有 宣告 ,無論是檢查還是一般形式,以及在 評估內容 中的所有一般形式 ;在 的情況下也如此。如果至少有一個運算子(§12.6.4.2)適用於參數清單 ,則候選運算子集合由 中所有這類適用的運算子所組成。
  • 否則,如果 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

  • 尋找一組適用的使用者定義和提升轉換運算子,U。 這個集合包含由類別或結構在 D 中宣告的使用者定義和提升的隱含或明確轉換運算符,這些運算符將從涵蓋或被 S 涵蓋的類型轉換成涵蓋或被 T涵蓋的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

將會替換成下列項目符號:

  • 尋找一組適用的使用者定義和提升的明確轉換運算子,U0。 這個集合是由使用者定義和提升的明確轉換運算子所組成,這些運算符是由 D中的類別或結構所宣告,在 checked 評估內容中,而且只有在 unchecked 評估內容中的一般形式,並且從 S 包含或包含的類型轉換成包含或包含 T所包含的類型。
  • 如果 U0 至少包含一個勾選形式中的運算符,則會從集合中移除所有常規形式中的運算符。
  • 尋找一組適用的使用者定義和提升轉換運算子,U。 這個集合包含來自 U0的運算元,以及 D 中類別或結構所宣告的使用者定義和提升隱含轉換運算元,這些運算符會從包含或包含 S 的類型轉換成包含或包含 T的類型。 如果 U 是空的,則轉換未定義,而且會發生編譯時期錯誤。

將會調整 checked 和 unchecked 運算子 \12.8.20 區段,以反映已核取/未核取內容對處理使用者定義明確轉換的影響。

已勾選與未勾選狀態在 checked operator

編譯程式可以將 checked operator 的預設內容視為已核取。 如果部分演算法不應參與 unchecked,開發人員必須明確使用 checked context。 不過,如果我們開始允許 checked/unchecked 標記作為運算符的修飾詞來設定主體內的語境,這在未來可能效果不佳。 修飾詞和 關鍵詞可能會相互矛盾。 此外,我們無法對 regular operator 採取相同的措施(將預設上下文視為未核取狀態),因為這將帶來不兼容的重大變更。

未解決的問題

語言應該允許在方法上使用 checkedunchecked 修飾詞嗎(例如 static checked void M())? 這將允許移除某些方法所需的巢狀層級。

Linq 運算式樹狀結構中檢查的除法

沒有工廠方法來建立檢查的除法節點,也沒有 ExpressionType.DivideChecked 成員。 我們仍然可以使用下列 Factory 方法來建立一般分割節點,並將 MethodInfo 指向 op_CheckedDivision 方法。 取用者必須檢查名稱以推斷內容。

public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);

請注意,儘管 \12.8.20 區段將 / 運算子列為受到已檢查/未檢查的評估上下文影響的運算子之一,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