已检查的用户定义的运算符

注释

本文是功能规格说明。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。

功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的 语言设计会议(LDM)记录中。

可以在有关 规范的文章中详细了解将功能规范采用 C# 语言标准的过程。

支持者问题:https://github.com/dotnet/csharplang/issues/4665

概要

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作(其中op可重载运算符和A参数列表),由其operator op(A)提供的T候选用户定义运算符集按operator op(A)如下方式确定:

  • 确定类型 T0。 如果 T 为可以为 null 的类型, T0 则为其基础类型,否则 T0 等于 T
  • 查找用户定义的运算符集。 U 此集包括:
    • unchecked 计算上下文中,所有常规 operator op 声明都在 T0.
    • checked评估上下文中T0,除具有成对匹配checked operator声明的常规声明之外的所有已检查声明和常规operator op声明。
  • 对于此类运算符的所有operator op声明和所有提升形式的声明U,如果至少一个运算符适用于参数列表A§12.4.6 - 适用的函数成员),则候选运算符集由所有此类适用运算符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 节,以反映已检查/未选中上下文对处理用户定义的显式转换的影响。

实现运算符

A 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 关键词

建议支持 unchecked 关键字的位置与 checked 关键字的位置相同,其含义如下:

  • 只需显式反映运算符的规律性,或
  • 也许要指定应在上下文中使用的 unchecked 运算符的独特风格。 该语言可以支持 op_Additionop_CheckedAddition并帮助 op_UncheckedAddition 限制中断性变更的数量。 这增加了大多数代码中可能不需要的另一层复杂性。

ECMA-335 中的运算符名称

或者,运算符名称可以是 op_UnaryNegationCheckedop_AdditionCheckedop_SubtractionCheckedop_MultiplyCheckedop_DivisionChecked,并在末尾 选中 。 但是,它看起来已经建立了一种模式,以用运算符单词结束名称。 例如,有 一个op_UnsignedRightShift 运算符,而不是 op_RightShiftUnsigned 运算符。

Checked operators 在上下文中 unchecked 不可应用

当执行成员查找以在上下文中 unchecked 查找候选用户定义运算符时,编译器可能会忽略 checked operators。 如果遇到仅定义 a 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 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 operatorunchecked计算上下文,checked operator匹配checked计算上下文和没有checked形式(例如+)的运算符匹配任一上下文,则 §12.4.4 中的第一个项目符号 - 一元运算符重载解析

将替换为以下两个项目符号点:

  • X与当前选中/未选中上下文匹配的作operator op(x)提供的候选用户定义运算符集是使用候选用户定义运算符规则确定的。
  • 如果候选用户定义运算符集不为空,则这将成为操作的候选运算符集。 否则,通过使用 §12.4.6 - 候选用户定义运算符的规则来确定匹配相反的选中/未选中上下文的作operator op(x)提供的X候选用户定义运算符集。

二元运算符重载解析

假设匹配regular operator计算上下文,checked operator匹配checked计算上下文和没有窗体(例如%)的运算符匹配任一checked上下文,第一个项目符号位于 §12.4.5 - 二进制运算符重载解析中:unchecked

  • 确定由 XY 提供的候选用户定义运算符集,以用于操作 operator op(x,y)。 该集由由 X 候选运算符提供的候选运算符和候选运算符的联合组成 Y,每个运算符均使用 §12.4.6 - 候选用户定义运算符的规则确定。 如果 XY 是相同的类型,或者如果 XY 派生自通用基类型,则共享候选运算符仅在组合集中出现一次。

将替换为以下两个项目符号点:

  • 确定由X当前Y选中/未选中上下文匹配的作operator op(x,y)的候选用户定义运算符集。 该集由由 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 operatorunchecked评估上下文匹配并checked operator匹配checked评估上下文,则第三个项目符号在 §10.5.3 中评估用户定义的转换

  • 查找适用的用户定义和提升转换运算符集 U。 该集合由 D 中的类或结构体声明的用户定义和解除的隐式或显式转换操作符组成,这些操作符将 S 包含或涵盖的类型转换为 T 包含或涵盖的类型。 如果 U 为空,则转换未定义且发生编译时错误。

将替换为以下项目符号点:

  • 查找与当前选中/未选中上下文U0匹配的适用用户定义和提升的显式转换运算符集。 此集由类或结构 D 声明的用户定义和提升显式转换运算符组成,这些运算符 与当前选中/未检查上下文匹配 ,并从包含或包含 S 的类型转换为包含或包含的类型 T
  • 查找与相反的选中/未选中上下文U1匹配的适用用户定义和提升的显式转换运算符集。 如果 U0 不是空, U1 则为空。 否则,此集由类或结构 D 声明的用户定义和提升显式转换运算符组成,这些运算符 与相反的选中/未选中上下文匹配 ,并从包含或包含 S 的类型转换为包含或包含的类型 T
  • 查找适用的用户定义和提升转换运算符集 U。 此集由来自 U0U1、由类或结构 D 声明的用户定义和提升的隐式转换运算符组成,这些运算符从包含或包含的类型转换为包含或包含 S 的类型 T。 如果 U 为空,则转换未定义且发生编译时错误。

另一种方法是生成候选用户定义运算符集

一元运算符重载解析

第 §12.4.4 节中的第一个项目符号将按如下方式进行调整(加粗)。

  • 为作operator op(x)提供的X候选用户定义运算符集是使用以下“候选用户定义运算符”部分的规则确定的。 如果集包含选中的窗体中至少有一个运算符,则会从集中删除常规窗体中的所有运算符。

§12.8.20 节将进行调整,以反映已检查/未选中上下文对一元运算符重载解析的影响。

二元运算符重载解析

第 §12.4.5 节中的第一个项目符号将按如下方式进行调整(加粗)。

  • 确定由 XY 提供的候选用户定义运算符集,以用于操作 operator op(x,y)。 该集由由 X 候选运算符提供的候选运算符和由 Y候选运算符提供的联合组成,每个运算符均使用下面的“候选用户定义运算符”部分的规则确定。 如果 XY 是相同的类型,或者如果 XY 派生自通用基类型,则共享候选运算符仅在组合集中出现一次。 如果集包含选中的窗体中至少有一个运算符,则会从集中删除常规窗体中的所有运算符。

将调整 Checked 和 unchecked 运算符 §12.8.20 节,以反映已选中/未选中上下文对二进制运算符重载解析的影响。

候选用户定义运算符

§12.4.6 - 候选用户定义运算符部分将按如下方式进行调整(加粗)。

给定类型和T作(其中op可重载运算符和A参数列表),由其operator op(A)提供的T候选用户定义运算符集按operator op(A)如下方式确定:

  • 确定类型 T0。 如果 T 为可以为 null 的类型, T0 则为其基础类型,否则 T0 等于 T
  • operator op对于在计算上下文中checked检查的所有声明和正则形式,并且仅在计算上下文中的unchecked正则形式和此类运算符的所有提升形式中,如果至少一个运算符适用于参数列表A§12.6.4.2),则候选运算符集由所有此类适用运算符T0组成。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 中的第三个项目符号:

  • 查找适用的用户定义和提升转换运算符集 U。 该集合由 D 中的类或结构体声明的用户定义和解除的隐式或显式转换操作符组成,这些操作符将 S 包含或涵盖的类型转换为 T 包含或涵盖的类型。 如果 U 为空,则转换未定义且发生编译时错误。

将替换为以下项目符号点:

  • 查找一组适用的用户定义的和提升的显式转换运算符。 U0 此集由在计算上下文中D由类或结构声明的用户定义和提升显式转换运算符组成,并且仅在计算上下文中的unchecked常规checked形式以及从包含或包含S的类型转换为包含或包含的类型T
  • 如果 U0 选中的窗体中至少包含一个运算符,则会从集中删除常规窗体中的所有运算符。
  • 查找适用的用户定义和提升转换运算符集 U。 此集由来自 的运算符 U0以及用户定义和提升的隐式转换运算符(由类或结构 D 声明)从包含或包含 S 的类型转换为包含或包含的类型所声明的 T隐式转换运算符。 如果 U 为空,则转换未定义且发生编译时错误。

将调整 Checked 和 unchecked 运算符 §12.8.20 节,以反映已检查/未检查上下文对处理用户定义的显式转换的影响。

选中的上下文与未选中的上下文 checked operator

编译器可以将默认上下文 checked operator 视为已选中。 如果部分算法不应参与,checked context开发人员将需要显式使用unchecked。 但是,如果我们开始允许 checked/unchecked 在正文中设置上下文的修饰符作为修饰符的标记,则将来这种情况可能不起作用。 修饰符和关键字可能相互矛盾。 此外,我们将无法执行相同的(将默认上下文视为未选中), regular operator 因为这将是一项中断性变更。

未解决的问题

语言是否允许 checkedunchecked 修改方法(例如 static checked void M())? 这将允许删除需要嵌套级别的方法。

Linq 表达式树中的检查除法

没有用于创建已检查分区节点且没有 ExpressionType.DivideChecked 成员的工厂方法。 我们仍然可以使用以下工厂方法创建常规划分节点,并 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