本机大小的整数
注意
本文是特性规范。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。
功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。
可以在有关规范的文章中了解更多有关将功能规范子块纳入 C# 语言标准的过程。
支持者问题:https://github.com/dotnet/csharplang/issues/435
总结
对本机大小的有符号和无符号整数类型提供了语言支持。
目的是满足互操作方案和低级别库的需求。
设计
标识符 nint
和 nuint
是表示本地有符号和无符号整数类型的新的上下文关键字。
只有在名称查找在该程序位置找不到可行结果时,标识符才会被视为关键字。
nint x = 3;
_ = nint.Equals(x, 3);
类型 nint
和 nuint
由基础类型 System.IntPtr
和 System.UIntPtr
表示,编译器会将针对这些类型的其他转换和运算作为本机整数来呈现。
常量
常量表达式可以是 nint
或 nuint
类型。
没有适用于本机整数文本的直接语法。 可以改为使用其他整数值的隐式或显式强制转换:const nint i = (nint)42;
。
nint
常量的范围为 [ int.MinValue
, int.MaxValue
]。
nuint
常量的范围为 [ uint.MinValue
, uint.MaxValue
]。
nint
或 nuint
上没有 MinValue
或 MaxValue
字段,因为除 nuint.MinValue
外,这些值不能作为常量发出。
所有的一元运算符 {+
、-
、~
} 和二元运算符 {+
、-
、*
、/
、%
、==
、!=
、<
、<=
、>
、>=
、&
、|
、^
、<<
、>>
} 都支持常量折叠。
常量折叠运算使用 Int32
和 UInt32
操作数而不是本机整数进行计算,以确保无论使用何种编译器平台都能实现一致的行为。
如果操作的结果是 32 位常量值,则在编译时执行常量折叠。
否则,该操作将在运行时执行,不会被视为常量。
转换
nint
和 IntPtr
之间,以及 nuint
和 UIntPtr
之间存在身份转换。
仅在本机整数和基础类型方面存在差异的复合类型之间存在标识转换:数组、Nullable<>
、构造类型和元组。
下表涵盖了特殊类型之间的转换。
(每种转换的 IL 都包括 unchecked
和 checked
上下文的变体(如果有所不同)。
有关下表的一般说明:
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 nint
和sbyte to nuint
使用conv.i
,而byte to nint
和byte to nuint
使用conv.u
,因为它们都是扩大转换。nint to byte
和nuint to byte
使用conv.u1
,而nint to sbyte
和nuint to sbyte
使用conv.i1
。 对于byte
、sbyte
、short
和ushort
,“堆栈类型 ”为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 的转换(如果存在从
A
到B
的标识转换或隐式转换); - 显式可为 null 的转换(如果存在从
A
到B
的显式转换); - 否则无效。
从 Nullable<A>
转换为 B
是:
- 显式可为 null 的转换(如果存在从
A
到B
的标识转换或者隐式或显式数值转换)。 - 否则无效。
从 Nullable<A>
转换为 Nullable<B>
是:
- 标识转换(如果存在从
A
到B
的标识转换); - 显式可为 null 的转换(如果存在从
A
到B
的隐式或显式数值转换); - 否则无效。
运算符
预定义运算符如下。
nint
如果至少有一个操作数为 nuint
或 类型,则根据隐式转换的常规规则,在重载决策期间会考虑这些运算符。
每个运算符的 IL 包括 unchecked
和 checked
上下文的变体(如果不同)。
一元 | 运算符签名 | 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
(其中 x
或 y
为本机整数)遵循的规则与具有预定义运算符的其他基元类型相同。
具体而言,表达式绑定为 x = (T)(x op y)
,其中 T
是 x
的类型,而 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# 中,没有用于与本机整数偏移量进行指针加法或减法运算的预定义运算符。
相反,nint
和 nuint
的值提升为 long
和 ulong
,指针运算对这些类型使用预定义的运算符。
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
;或者如果另一个操作数为sbyte
、short
、int
、nint
或long
,则会发生绑定时错误。- 否则,如果任一操作数的类型为
nuint
,则另一个操作数将转换为类型nuint
;或者如果另一个操作数为sbyte
、short
、int
、nint
或long
,则会发生绑定时错误。- 否则,如果任一操作数是
long
类型,则将另一个操作数转换为long
类型。- 否则,如果任一操作数的类型为
uint
,而另一个操作数的类型为sbyte
、short
、nint
、 或int
,则两个操作数将转换为类型long
。- 否则,如果任一操作数是
uint
类型,则将另一个操作数转换为uint
类型。- 否则,如果任一操作数是
nint
类型,则将另一个操作数转换为nint
类型。- 否则,两个操作数将转换为
int
类型。
动态
转换和运算符由编译器合成,不属于基础 IntPtr
和 UIntPtr
类型。
因此,在 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'
类型成员
nint
或 nuint
的唯一构造函数是无参数构造函数。
System.IntPtr
和 System.UIntPtr
的以下成员已从 nint
或 nuint
中显式排除。
// 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.IntPtr
和 System.UIntPtr
的其余成员已隐式包含在 nint
和 nuint
中。 对于 .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.IntPtr
和 System.UIntPtr
实现的接口已隐式包含在 nint
和 nuint
中,并且出现的基础类型会替换为相应本机整数类型。
例如,如果 IntPtr
实现 ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
,那么 nint
就会实现 ISerializable, IEquatable<nint>, IComparable<nint>
。
重写、隐藏和实现
nint
与 System.IntPtr
以及 nuint
与 System.UIntPtr
在重写、隐藏和实现中被认为是等效的。
重载不会仅因 nint
与 System.IntPtr
或者 nuint
与 System.UIntPtr
的不同而有所差异。
重写和实现可能会仅因 nint
与 System.IntPtr
或者 nuint
与 System.UIntPtr
的不同而有所差异。
方法会隐藏仅因 nint
与 System.IntPtr
或者 nuint
与 System.UIntPtr
的不同而有所差异的其他方法。
杂项
用作数组索引的 nint
和 nuint
表达式会在不进行转换的情况下发出。
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
和 nuint
不能用作 C# 的 enum
基类型。
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
对于 nint
和 nuint
,读取和写入是原子级的。
对于类型 volatile
和 nint
,可以将字段标记为 nuint
。
然而,ECMA-334 15.5.4 不包括基类型为 enum
或 System.IntPtr
的 System.UIntPtr
。
default(nint)
和 new nint()
等效于 (nint)0
;default(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
的形式实现。
涉及 nint
和 nuint
的类型引用的编译器诊断会报告 nint
或 nuint
,而不是 IntPtr
或 UIntPtr
。
元数据
nint
和 nuint
在元数据中表示为 System.IntPtr
和 System.UIntPtr
。
包含 nint
或 nuint
的类型引用会使用 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.NativeInt
和 System.NativeUInt
。
public readonly struct NativeInt
{
public IntPtr Value;
}
不同的类型允许进行不同于 IntPtr
的重载,并且允许进行不同的分析和 ToString()
。
但是,CLR 要高效地处理这些类型需付出更多努力,这违背了该特性的主要目的——提高效率。
与使用 IntPtr
的现有本机整数代码进行互操作会变得更加困难。
另一种替代方法是在框架中进一步增加对 IntPtr
的本机整数支持,但不提供任何特定的编译器支持。
编译器将自动支持任何新的转换和算术运算。
但这种语言不会提供关键字、常量或 checked
操作。
设计会议
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-26.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-06-13.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-07-05.md#native-int-and-intptr-operators
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-10-23.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-03-25.md