无符号右移运算符

注意

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

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

可以在有关规范的文章中了解更多有关将功能规范子块纳入 C# 语言标准的过程。

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

总结

C# 将支持无符号右移运算符作为内置运算符(用于基元整型类型)以及用户定义的运算符。

动机

在处理带符号整数值时,通常需要向右移位而不复制高位的符号位。 虽然对于具有常规移位运算符的基元整型类型,可以实现这一操作,但需要在移位操作前将其转换为无符号类型,并在移位操作后再转换回原类型。 在库计划要开放的泛型数学接口中,这可能会更具挑战性,因为类型未必有一个预先定义或已知的无符号对应类型,而算法可能依赖于执行无符号右移操作的能力。

详细设计

运算符和标点符号

§6.4.6 节将进行调整,以包括 >>> 运算符 - 无符号右移运算符:

unsigned_right_shift
    : '>>>'
    ;

unsigned_right_shift_assignment
    : '>>>='
    ;

unsigned_right_shiftunsigned_right_shift_assignment 这两个表达式的令牌之间,不允许出现任何字符(甚至空格)。 这些生成项经过特别处理,以便正确处理 type_parameter_list

移位运算符

§6.4.6 节将进行调整,以包括 >>> 运算符 - 无符号右移运算符:

<<>>>>> 运算符用于执行位移运算。

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    | shift_expression unsigned_right_shift additive_expression
    ;

对于表单 x << countx >> countx >>> count 的操作,将应用二进制运算符重载解析 (§12.4.5) 来选择特定的运算符实现。 操作数转换为所选运算符的参数类型,结果的类型是运算符的返回类型。

预定义的无符号移位运算符将在当前实现中支持与预定义的有符号移位运算符相同的签名集合。

  • 右移:

    int operator >>>(int x, int count);
    uint operator >>>(uint x, int count);
    long operator >>>(long x, int count);
    ulong operator >>>(ulong x, int count);
    nint operator >>>(nint x, int count);
    nuint operator >>>(nuint x, int count);
    

    >>>运算符x将按照下面描述的方式右移若干位。

    丢弃 x 的低序位,将剩余位向右移,并将高阶空出的位置设为零。

对于预定义的运算符,要移动的位数计算如下:

  • x 的类型为 intuint时,移位计数由 count的低序五位决定。 换句话说,移位计数是从 count & 0x1F计算得出的。
  • x 的类型为 longulong时,移位计数由 count的低序六位决定。 换句话说,移位计数是从 count & 0x3F计算得出的。

如果生成的移位计数为零,则移位运算符直接返回 x的值。

移位运算绝对不会导致溢出并在checkedunchecked上下文中产生相同结果。

赋值运算符

§12.21 将被调整以包括 无符号右移赋值,如下所示:

assignment_operator
    : '='
    | '+='
    | '-='
    | '*='
    | '/='
    | '%='
    | '&='
    | '|='
    | '^='
    | '<<='
    | right_shift_assignment
    | unsigned_right_shift_assignment
    ;

整型

“整型类型”§8.3.6 节将被调整,以包含关于 >>> 运算符的信息。 相关的项目符号点如下:

  • 对于二进制 <<>>>>> 运算符,左操作数将转换为类型 T,其中 Tintuintlongulong 中第一个可以完全表示操作数所有可能值的类型。 然后,使用 T类型的精度来执行该操作,结果的类型是 T

常量表达式

>>>中,运算符 将被添加到允许常量表达式的构造集。

运算符重载

运算符 >>> 将添加到 §12.4.3 中的可重载二进制运算符集中。

提升的运算符

运算符 >>> 将被添加到 §12.4.8 中允许提升形式的二进制运算符集合中。

运算符优先级和关联性

将调整第 §12.4.2 节,在“移位”类别中添加 >>> 运算符,在“赋值和 lambda 表达式”类别中添加 >>>= 运算符。

语法歧义

>>> 运算符受到与常规 运算符在 >> 中所描述的相同语法歧义的影响。

运算符

§15.10 节将进行调整以包含 >>> 运算符。

overloadable_binary_operator
    : '+'   | '-'   | '*'   | '/'   | '%'   | '&'   | '|'   | '^'   | '<<'
    | right_shift | unsigned_right_shift | '=='  | '!='  | '>'   | '<'   | '>='  | '<='
    ;

二元运算符

>>> 运算符的签名受制于与 §15.10.3 所述 >> 运算符签名相同的规则。

元数据名称

ECMA-335 的“I.10.3.2 二进制运算符”部分已为无符号右移运算符保留了名称 op_UnsignedRightShift。

Linq 表达式树

在 Linq 表达式树中不支持 >>> 运算符,因为要准确表示有符号类型上预定义的 >>> 运算符的语义,需要对无符号类型进行转换再返回,而这无法实现。 有关详细信息,请参阅https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md#unsigned-right-shift-operator

动态绑定

动态绑定似乎使用 System.Linq.Expressions.ExpressionType 枚举的值来向运行时绑定器传达二进制运算符类型。 由于我们没有专门代表无符号右移运算符的成员,因此针对 >>> 运算符的动态绑定将不被支持,并且将调整静态和动态绑定(§12.3)部分以反映这一点。

缺点

替代方案

Linq 表达式树

Linq 表达式树中将支持 >>> 运算符。

  • 对于用户定义的运算符,将创建一个指向运算符方法的二元表达式节点。
  • 对于预定义运算符
    • 当第一个操作数是无符号类型时,将创建 BinaryExpression 节点。
    • 当第一个操作数是签名类型时,将添加第一个操作数到未签名类型的转换,将创建 BinaryExpression 节点,并将结果转换回签名类型。

例如:

Expression<System.Func<int, int, int>> z = (x, y) => x >>> y; // (x, y) => Convert((Convert(x, UInt32) >> y), Int32)

Resolution:

已拒绝,请参阅https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md#unsigned-right-shift-operator以了解更多信息。

未解决的问题

设计会议

https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md