注释
本文是功能规格说明。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。
功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的 语言设计会议(LDM)记录中。
可以在有关 规范的文章中详细了解将功能规范采用 C# 语言标准的过程。
支持者问题:https://github.com/dotnet/csharplang/issues/4665
概要
C# 应支持定义 checked
以下用户定义的运算符的变体,以便用户可以根据需要选择进入或退出溢出行为:
动机
用户无法声明类型并支持已选中和未选中的运算符版本。 这样就很难移植各种算法来使用库团队公开的建议 generic math
接口。 同样,这样就不可能公开一种类型,例如 Int128
或 UInt128
没有语言同时传送其自己的支持,以避免重大更改。
详细设计
语法
运算符(§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_CheckedIncrement、op_CheckedDecrement、op_CheckedUnaryNegation作为实现已检查++
--
的方法的名称和-
一元运算符。
ECMA-335 的“I.10.3.2 二进制运算符”部分将进行调整,以包括op_CheckedAddition、op_CheckedSubtraction、op_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
组成。 - 否则,如果
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 中的第三个项目符号:
- 查找适用的用户定义和提升转换运算符集
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_Addition
,op_CheckedAddition
并帮助op_UncheckedAddition
限制中断性变更的数量。 这增加了大多数代码中可能不需要的另一层复杂性。
ECMA-335 中的运算符名称
或者,运算符名称可以是 op_UnaryNegationChecked、 op_AdditionChecked、 op_SubtractionChecked、 op_MultiplyChecked、 op_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 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 中的第一个项目符号 - 一元运算符重载解析:
- 为作提供的候选用户定义运算符集是使用 §12.4.6 - 候选用户定义运算符的规则确定的。
X
operator op(x)
将替换为以下两个项目符号点:
- 为
X
与当前选中/未选中上下文匹配的作operator op(x)
提供的候选用户定义运算符集是使用候选用户定义运算符规则确定的。 - 如果候选用户定义运算符集不为空,则这将成为操作的候选运算符集。 否则,通过使用 §12.4.6 - 候选用户定义运算符的规则来确定匹配相反的选中/未选中上下文的作
operator op(x)
提供的X
候选用户定义运算符集。
二元运算符重载解析
假设匹配regular operator
计算上下文,checked operator
匹配checked
计算上下文和没有窗体(例如%
)的运算符匹配任一checked
上下文,第一个项目符号位于 §12.4.5 - 二进制运算符重载解析中:unchecked
- 确定由
X
和Y
提供的候选用户定义运算符集,以用于操作operator op(x,y)
。 该集由由X
候选运算符提供的候选运算符和候选运算符的联合组成Y
,每个运算符均使用 §12.4.6 - 候选用户定义运算符的规则确定。 如果X
和Y
是相同的类型,或者如果X
和Y
派生自通用基类型,则共享候选运算符仅在组合集中出现一次。
将替换为以下两个项目符号点:
- 确定由
X
当前Y
选中/未选中上下文匹配的作operator op(x,y)
的候选用户定义运算符集。 该集由由X
候选运算符提供的候选运算符和候选运算符的联合组成Y
,每个运算符均使用 §12.4.6 - 候选用户定义运算符的规则确定。 如果X
和Y
是相同的类型,或者如果X
和Y
派生自通用基类型,则共享候选运算符仅在组合集中出现一次。 - 如果候选用户定义运算符集不为空,则这将成为操作的候选运算符集。 否则,将确定由
X
Y
和匹配相反的选中/未选中上下文的作operator op(x,y)
的候选用户定义运算符集。 该集由由X
候选运算符提供的候选运算符和候选运算符的联合组成Y
,每个运算符均使用 §12.4.6 - 候选用户定义运算符的规则确定。 如果X
和Y
是相同的类型,或者如果X
和Y
派生自通用基类型,则共享候选运算符仅在组合集中出现一次。
示例 #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 中评估用户定义的转换:
- 查找适用的用户定义和提升转换运算符集
U
。 该集合由D
中的类或结构体声明的用户定义和解除的隐式或显式转换操作符组成,这些操作符将S
包含或涵盖的类型转换为T
包含或涵盖的类型。 如果U
为空,则转换未定义且发生编译时错误。
将替换为以下项目符号点:
- 查找与当前选中/未选中上下文
U0
匹配的适用用户定义和提升的显式转换运算符集。 此集由类或结构D
声明的用户定义和提升显式转换运算符组成,这些运算符 与当前选中/未检查上下文匹配 ,并从包含或包含S
的类型转换为包含或包含的类型T
。 - 查找与相反的选中/未选中上下文
U1
匹配的适用用户定义和提升的显式转换运算符集。 如果U0
不是空,U1
则为空。 否则,此集由类或结构D
声明的用户定义和提升显式转换运算符组成,这些运算符 与相反的选中/未选中上下文匹配 ,并从包含或包含S
的类型转换为包含或包含的类型T
。 - 查找适用的用户定义和提升转换运算符集
U
。 此集由来自U0
、U1
、由类或结构D
声明的用户定义和提升的隐式转换运算符组成,这些运算符从包含或包含的类型转换为包含或包含S
的类型T
。 如果U
为空,则转换未定义且发生编译时错误。
另一种方法是生成候选用户定义运算符集
一元运算符重载解析
第 §12.4.4 节中的第一个项目符号将按如下方式进行调整(加粗)。
- 为作
operator op(x)
提供的X
候选用户定义运算符集是使用以下“候选用户定义运算符”部分的规则确定的。 如果集包含选中的窗体中至少有一个运算符,则会从集中删除常规窗体中的所有运算符。
第 §12.8.20 节将进行调整,以反映已检查/未选中上下文对一元运算符重载解析的影响。
二元运算符重载解析
第 §12.4.5 节中的第一个项目符号将按如下方式进行调整(加粗)。
- 确定由
X
和Y
提供的候选用户定义运算符集,以用于操作operator op(x,y)
。 该集由由X
候选运算符提供的候选运算符和由Y
候选运算符提供的联合组成,每个运算符均使用下面的“候选用户定义运算符”部分的规则确定。 如果X
和Y
是相同的类型,或者如果X
和Y
派生自通用基类型,则共享候选运算符仅在组合集中出现一次。 如果集包含选中的窗体中至少有一个运算符,则会从集中删除常规窗体中的所有运算符。
将调整 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
- 否则,如果
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 中的第三个项目符号:
- 查找适用的用户定义和提升转换运算符集
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
因为这将是一项中断性变更。
未解决的问题
语言是否允许 checked
和 unchecked
修改方法(例如 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 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