本机大小的整数

注意

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

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

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

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

总结

对本机大小的有符号和无符号整数类型提供了语言支持。

目的是满足互操作方案和低级别库的需求。

设计

标识符 nintnuint 是表示本地有符号和无符号整数类型的新的上下文关键字。 只有在名称查找在该程序位置找不到可行结果时,标识符才会被视为关键字。

nint x = 3;
_ = nint.Equals(x, 3);

类型 nintnuint 由基础类型 System.IntPtrSystem.UIntPtr 表示,编译器会将针对这些类型的其他转换和运算作为本机整数来呈现。

常量

常量表达式可以是 nintnuint 类型。 没有适用于本机整数文本的直接语法。 可以改为使用其他整数值的隐式或显式强制转换:const nint i = (nint)42;

nint 常量的范围为 [ int.MinValue, int.MaxValue ]。

nuint 常量的范围为 [ uint.MinValue, uint.MaxValue ]。

nintnuint 上没有 MinValueMaxValue 字段,因为除 nuint.MinValue 外,这些值不能作为常量发出。

所有的一元运算符 {+-~} 和二元运算符 {+-*/%==!=<<=>>=&|^<<>>} 都支持常量折叠。 常量折叠运算使用 Int32UInt32 操作数而不是本机整数进行计算,以确保无论使用何种编译器平台都能实现一致的行为。 如果操作的结果是 32 位常量值,则在编译时执行常量折叠。 否则,该操作将在运行时执行,不会被视为常量。

转换

nintIntPtr之间,以及 nuintUIntPtr之间存在身份转换。 仅在本机整数和基础类型方面存在差异的复合类型之间存在标识转换:数组、Nullable<>、构造类型和元组。

下表涵盖了特殊类型之间的转换。 (每种转换的 IL 都包括 uncheckedchecked 上下文的变体(如果有所不同)。

有关下表的一般说明:

  • conv.u 是通过零扩展转换来转换为本机整数,conv.i 是通过符号扩展转换来转换为本机整数。
  • 扩大转换和收缩转换的 checked 上下文为:
    • conv.ovf.* 用于 signed to *
    • conv.ovf.*.un 用于 unsigned to *
  • 扩大转换的 unchecked 上下文为:
    • conv.i* 用于 signed to *(其中 * 为目标宽度)
    • conv.u* 用于 unsigned to *(其中 * 为目标宽度)
  • 收缩转换的 unchecked 上下文为:
    • conv.i* 用于 any to signed *(其中 * 为目标宽度)
    • conv.u* 用于 any to unsigned *(其中 * 为目标宽度)

举几个例子:

  • sbyte to nintsbyte to nuint 使用 conv.i,而 byte to nintbyte to nuint 使用 conv.u,因为它们都是扩大转换
  • nint to bytenuint to byte 使用 conv.u1,而 nint to sbytenuint to sbyte 使用 conv.i1。 对于 bytesbyteshortushort,“堆栈类型 ”为 int32。 因此,conv.i1 实际上是“向下转换为有符号字节,然后进行符号扩展,直至成为 int32”,而 conv.u1 实际上是“向下转换为无符号字节,然后进行零扩展,直至成为 int32”。
  • checked void* to nint 使用 conv.ovf.i.un 的方式与 checked void* to long 使用 conv.ovf.i8.un 的方式相同。
操作数 目标 转换 IL
object nint 取消装箱 unbox
void* nint PointerToVoid nop / conv.ovf.i.un
sbyte nint ImplicitNumeric conv.i
byte nint ImplicitNumeric conv.u
short nint ImplicitNumeric conv.i
ushort nint ImplicitNumeric conv.u
int nint ImplicitNumeric conv.i
uint nint ExplicitNumeric conv.u / conv.ovf.i.un
long nint ExplicitNumeric conv.i / conv.ovf.i
ulong nint ExplicitNumeric conv.i / conv.ovf.i.un
char nint ImplicitNumeric conv.u
float nint ExplicitNumeric conv.i / conv.ovf.i
double nint ExplicitNumeric conv.i / conv.ovf.i
decimal nint ExplicitNumeric long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i
IntPtr nint 标识
UIntPtr nint
object nuint 取消装箱 unbox
void* nuint PointerToVoid nop
sbyte nuint ExplicitNumeric conv.i / conv.ovf.u
byte nuint ImplicitNumeric conv.u
short nuint ExplicitNumeric conv.i / conv.ovf.u
ushort nuint ImplicitNumeric conv.u
int nuint ExplicitNumeric conv.i / conv.ovf.u
uint nuint ImplicitNumeric conv.u
long nuint ExplicitNumeric conv.u / conv.ovf.u
ulong nuint ExplicitNumeric conv.u / conv.ovf.u.un
char nuint ImplicitNumeric conv.u
float nuint ExplicitNumeric conv.u / conv.ovf.u
double nuint ExplicitNumeric conv.u / conv.ovf.u
decimal nuint ExplicitNumeric ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un
IntPtr nuint
UIntPtr nuint 标识
枚举 nint ExplicitEnumeration
枚举 nuint ExplicitEnumeration
操作数 目标 转换 IL
nint object 装箱 box
nint void* PointerToVoid nop / conv.ovf.u
nint nuint ExplicitNumeric conv.u(可以省略) / conv.ovf.u
nint sbyte ExplicitNumeric conv.i1 / conv.ovf.i1
nint byte ExplicitNumeric conv.u1 / conv.ovf.u1
nint short ExplicitNumeric conv.i2 / conv.ovf.i2
nint ushort ExplicitNumeric conv.u2 / conv.ovf.u2
nint int ExplicitNumeric conv.i4 / conv.ovf.i4
nint uint ExplicitNumeric conv.u4 / conv.ovf.u4
nint long ImplicitNumeric conv.i8
nint ulong ExplicitNumeric conv.i8 / conv.ovf.u8
nint char ExplicitNumeric conv.u2 / conv.ovf.u2
nint float ImplicitNumeric conv.r4
nint double ImplicitNumeric conv.r8
nint decimal ImplicitNumeric conv.i8 decimal decimal.op_Implicit(long)
nint IntPtr 标识
nint UIntPtr
nint 枚举 ExplicitEnumeration
nuint object 装箱 box
nuint void* PointerToVoid nop
nuint nint ExplicitNumeric conv.i(可以省略) / conv.ovf.i.un
nuint sbyte ExplicitNumeric conv.i1 / conv.ovf.i1.un
nuint byte ExplicitNumeric conv.u1 / conv.ovf.u1.un
nuint short ExplicitNumeric conv.i2 / conv.ovf.i2.un
nuint ushort ExplicitNumeric conv.u2 / conv.ovf.u2.un
nuint int ExplicitNumeric conv.i4 / conv.ovf.i4.un
nuint uint ExplicitNumeric conv.u4 / conv.ovf.u4.un
nuint long ExplicitNumeric conv.u8 / conv.ovf.i8.un
nuint ulong ImplicitNumeric conv.u8
nuint char ExplicitNumeric conv.u2 / conv.ovf.u2.un
nuint float ImplicitNumeric conv.r.un conv.r4
nuint double ImplicitNumeric conv.r.un conv.r8
nuint decimal ImplicitNumeric conv.u8 decimal decimal.op_Implicit(ulong)
nuint IntPtr
nuint UIntPtr 标识
nuint 枚举 ExplicitEnumeration

A 转换为 Nullable<B> 是:

  • 隐式可为 null 的转换(如果存在从 AB 的标识转换或隐式转换);
  • 显式可为 null 的转换(如果存在从 AB 的显式转换);
  • 否则无效。

Nullable<A> 转换为 B 是:

  • 显式可为 null 的转换(如果存在从 AB 的标识转换或者隐式或显式数值转换)。
  • 否则无效。

Nullable<A> 转换为 Nullable<B> 是:

  • 标识转换(如果存在从 AB 的标识转换);
  • 显式可为 null 的转换(如果存在从 AB 的隐式或显式数值转换);
  • 否则无效。

运算符

预定义运算符如下。 nint如果至少有一个操作数为 nuint 类型,则根据隐式转换的常规规则,在重载决策期间会考虑这些运算符。

每个运算符的 IL 包括 uncheckedchecked 上下文的变体(如果不同)。

一元 运算符签名 IL
+ nint operator +(nint value) nop
+ nuint operator +(nuint value) nop
- nint operator -(nint value) neg
~ nint operator ~(nint value) not
~ nuint operator ~(nuint value) not
二进制 运算符签名 IL
+ nint operator +(nint left, nint right) add / add.ovf
+ nuint operator +(nuint left, nuint right) add / add.ovf.un
- nint operator -(nint left, nint right) sub / sub.ovf
- nuint operator -(nuint left, nuint right) sub / sub.ovf.un
* nint operator *(nint left, nint right) mul / mul.ovf
* nuint operator *(nuint left, nuint right) mul / mul.ovf.un
/ nint operator /(nint left, nint right) div
/ nuint operator /(nuint left, nuint right) div.un
% nint operator %(nint left, nint right) rem
% nuint operator %(nuint left, nuint right) rem.un
== bool operator ==(nint left, nint right) beq / ceq
== bool operator ==(nuint left, nuint right) beq / ceq
!= bool operator !=(nint left, nint right) bne
!= bool operator !=(nuint left, nuint right) bne
< bool operator <(nint left, nint right) blt / clt
< bool operator <(nuint left, nuint right) blt.un / clt.un
<= bool operator <=(nint left, nint right) ble
<= bool operator <=(nuint left, nuint right) ble.un
> bool operator >(nint left, nint right) bgt / cgt
> bool operator >(nuint left, nuint right) bgt.un / cgt.un
>= bool operator >=(nint left, nint right) bge
>= bool operator >=(nuint left, nuint right) bge.un
& nint operator &(nint left, nint right) and
& nuint operator &(nuint left, nuint right) and
| nint operator |(nint left, nint right) or
| nuint operator |(nuint left, nuint right) or
^ nint operator ^(nint left, nint right) xor
^ nuint operator ^(nuint left, nuint right) xor
<< nint operator <<(nint left, int right) shl
<< nuint operator <<(nuint left, int right) shl
>> nint operator >>(nint left, int right) shr
>> nuint operator >>(nuint left, int right) shr.un

对于某些二进制运算符,IL 运算符支持额外的操作数类型(请参阅 ECMA-335 III.1.5 操作数类型表)。 但是,C# 支持的操作符类型是有限的,这是为了简单起见,也是为了与语言中现有的操作符保持一致。

支持运算符的提升版本,其中参数和返回类型为 nint?nuint?

复合赋值运算 x op= y(其中 xy 为本机整数)遵循的规则与具有预定义运算符的其他基元类型相同。 具体而言,表达式绑定为 x = (T)(x op y),其中 Tx 的类型,而 x 只求值一次。

移位运算符应对要移位的位数进行掩码处理—如果 sizeof(nint) 为 4,则掩码为 5 位;如果 sizeof(nint) 为 8,则掩码为 6 位。 (请参阅 C# 规范中的 §12.11)。

如果使用早期语言版本进行编译,则 C#9 编译器会在绑定到预定义本机整数运算符时报错,但允许使用预定义转换(不管是转换为本机整数还是从本机整数转换)。

csc -langversion:9 -t:library A.cs

public class A
{
    public static nint F;
}

csc -langversion:8 -r:A.dll B.cs

class B : A
{
    static void Main()
    {
        F = F + 1; // error: nint operator+ not available with -langversion:8
        F = (System.IntPtr)F + 1; // ok
    }
}

指针算术

在 C# 中,没有用于与本机整数偏移量进行指针加法或减法运算的预定义运算符。 相反,nintnuint 的值提升为 longulong,指针运算对这些类型使用预定义的运算符。

static T* AddLeftS(nint x, T* y) => x + y;   // T* operator +(long left, T* right)
static T* AddLeftU(nuint x, T* y) => x + y;  // T* operator +(ulong left, T* right)
static T* AddRightS(T* x, nint y) => x + y;  // T* operator +(T* left, long right)
static T* AddRightU(T* x, nuint y) => x + y; // T* operator +(T* left, ulong right)
static T* SubRightS(T* x, nint y) => x - y;  // T* operator -(T* left, long right)
static T* SubRightU(T* x, nuint y) => x - y; // T* operator -(T* left, ulong right)

二元数值提升

二元数值提升相关内容(请参阅 C# 规范中的 §12.4.7.3)的更新如下:

  • 否则,如果任一操作数的类型为 ulong,则另一个操作数将转换为类型 ulong;或者如果另一个操作数为 sbyteshortintnintlong,则会发生绑定时错误。
  • 否则,如果任一操作数的类型为 nuint,则另一个操作数将转换为类型 nuint;或者如果另一个操作数为 sbyteshortintnintlong,则会发生绑定时错误。
  • 否则,如果任一操作数是 long 类型,则将另一个操作数转换为 long 类型。
  • 否则,如果任一操作数的类型为 uint,而另一个操作数的类型为 sbyteshortnintint,则两个操作数将转换为类型 long
  • 否则,如果任一操作数是 uint 类型,则将另一个操作数转换为 uint 类型。
  • 否则,如果任一操作数是 nint 类型,则将另一个操作数转换为 nint 类型。
  • 否则,两个操作数将转换为 int 类型。

动态

转换和运算符由编译器合成,不属于基础 IntPtrUIntPtr 类型。 因此,在 dynamic 的运行时绑定器中,这些转换和运算符不可用

nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'

类型成员

nintnuint 的唯一构造函数是无参数构造函数。

System.IntPtrSystem.UIntPtr 的以下成员已从 nintnuint 中显式排除

// constructors
// arithmetic operators
// implicit and explicit conversions
public static readonly IntPtr Zero; // use 0 instead
public static int Size { get; }     // use sizeof() instead
public static IntPtr Add(IntPtr pointer, int offset);
public static IntPtr Subtract(IntPtr pointer, int offset);
public int ToInt32();
public long ToInt64();
public void* ToPointer();

System.IntPtrSystem.UIntPtr 的其余成员已隐式包含nintnuint 中。 对于 .NET Framework 4.7.2:

public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);

System.IntPtrSystem.UIntPtr 实现的接口已隐式包含nintnuint 中,并且出现的基础类型会替换为相应本机整数类型。 例如,如果 IntPtr 实现 ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>,那么 nint 就会实现 ISerializable, IEquatable<nint>, IComparable<nint>

重写、隐藏和实现

nintSystem.IntPtr 以及 nuintSystem.UIntPtr 在重写、隐藏和实现中被认为是等效的。

重载不会仅因 nintSystem.IntPtr 或者 nuintSystem.UIntPtr 的不同而有所差异。 重写和实现可能会仅因 nintSystem.IntPtr 或者 nuintSystem.UIntPtr 的不同而有所差异。 方法会隐藏仅因 nintSystem.IntPtr 或者 nuintSystem.UIntPtr 的不同而有所差异的其他方法。

杂项

用作数组索引的 nintnuint 表达式会在不进行转换的情况下发出。

static object GetItem(object[] array, nint index)
{
    return array[index]; // ok
}

nintnuint 不能用作 C# 的 enum 基类型。

enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}

对于 nintnuint,读取和写入是原子级的。

对于类型 volatilenint,可以将字段标记为 nuint。 然而,ECMA-334 15.5.4 不包括基类型为 enumSystem.IntPtrSystem.UIntPtr

default(nint)new nint() 等效于 (nint)0default(nuint)new nuint() 等效于 (nuint)0

typeof(nint)typeof(IntPtr)typeof(nuint)typeof(UIntPtr)

虽然支持 sizeof(nint)sizeof(nuint),但需要在不安全的上下文中进行编译(sizeof(IntPtr)sizeof(UIntPtr) 也需要这样做)。 这些值不是编译时常量。 sizeof(nint)sizeof(IntPtr) 而不是 IntPtr.Size 的形式实现;sizeof(nuint)sizeof(UIntPtr) 而不是 UIntPtr.Size 的形式实现。

涉及 nintnuint 的类型引用的编译器诊断会报告 nintnuint,而不是 IntPtrUIntPtr

元数据

nintnuint 在元数据中表示为 System.IntPtrSystem.UIntPtr

包含 nintnuint 的类型引用会使用 System.Runtime.CompilerServices.NativeIntegerAttribute 发出,以指示类型引用的哪些部分是本机整数。

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class |
        AttributeTargets.Event |
        AttributeTargets.Field |
        AttributeTargets.GenericParameter |
        AttributeTargets.Parameter |
        AttributeTargets.Property |
        AttributeTargets.ReturnValue,
        AllowMultiple = false,
        Inherited = false)]
    public sealed class NativeIntegerAttribute : Attribute
    {
        public NativeIntegerAttribute()
        {
            TransformFlags = new[] { true };
        }
        public NativeIntegerAttribute(bool[] flags)
        {
            TransformFlags = flags;
        }
        public readonly bool[] TransformFlags;
    }
}

NativeIntegerAttribute 中涵盖带有 的类型引用的编码。

替代方案

除上述“类型擦除”方法外,另一种方法是引入新类型:System.NativeIntSystem.NativeUInt

public readonly struct NativeInt
{
    public IntPtr Value;
}

不同的类型允许进行不同于 IntPtr 的重载,并且允许进行不同的分析和 ToString()。 但是,CLR 要高效地处理这些类型需付出更多努力,这违背了该特性的主要目的——提高效率。 与使用 IntPtr 的现有本机整数代码进行互操作会变得更加困难。

另一种替代方法是在框架中进一步增加对 IntPtr 的本机整数支持,但不提供任何特定的编译器支持。 编译器将自动支持任何新的转换和算术运算。 但这种语言不会提供关键字、常量或 checked 操作。

设计会议