原生大小的整數
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在的相關
總結
原生帶正負號和不帶正負號整數類型的語言支援。
動機適用於互操作性案例及低階函式庫。
設計
標識碼 nint
和 nuint
是代表原生帶正負號和不帶正負號整數類型的新內容關鍵詞。
只有在名稱查閱在該程式位置找不到可行的結果時,才會將標識碼視為關鍵詞。
nint x = 3;
_ = nint.Equals(x, 3);
類型 nint
和 nuint
是由基礎類型 System.IntPtr
和 System.UIntPtr
來表示,編譯器會將這些類型的額外轉換和操作視為原生整數。
常數
常數表示式的類型可以是 nint
或 nuint
。
原生 int 字面值沒有直接的語法。 您可以改用其他整數常數值的隱含或明確轉換:const nint i = (nint)42;
。
nint
常數位於範圍 [ int.MinValue
, int.MaxValue
]。
nuint
常數位於範圍 [ uint.MinValue
, uint.MaxValue
]。
nint
或 nuint
上沒有 MinValue
或 MaxValue
字段,因為除了 nuint.MinValue
以外,這些值無法發出為常數。
所有一元運算符 { +
、-
、~
} 和二元運算符 { +
、-
、*
、/
、%
、==
、!=
、<
、<=
、>
、>=
、&
、|
、^
、<<
、>>
} 都支援常量折疊。
不論編譯器平台為何,都會使用 Int32
和 UInt32
操作數來進行常數折疊運算,以確保一致的行為,而不是使用原生 int。
如果作業產生 32 位的常數值,則會在編譯階段執行常數折疊。
否則,作業會在運行時間執行,而不會被視為常數。
轉換
nint
與 IntPtr
之間,以及 nuint
與 UIntPtr
之間有同一性轉換。
只有在原生 int 和基礎類型不同的情況下,複合類型之間存在識別轉換:陣列、Nullable<>
、構建類型和 Tuple。
下表涵蓋特殊類型之間的轉換。
(每個轉換的 IL 都包含 unchecked
和 checked
上下文的變體,如果這些上下文有所不同。)
下表的一般注意事項:
-
conv.u
是轉換到原生整數的零擴展,conv.i
是轉換到原生整數的符號擴展。 -
擴大 和 縮小 的
checked
情境如下:- 為
signed to *
使用的conv.ovf.*
- 用於
unsigned to *
的conv.ovf.*.un
- 為
-
擴大 的
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 |
空指標 | 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 |
指向空的指標 | 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 |
明確列舉 | |
列舉 | nuint |
顯式列舉 |
操作數 | 目標 | 轉換 | IL |
---|---|---|---|
nint |
object |
拳擊 | box |
nint |
void* |
指向空值的指標 | 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 |
列舉 | 顯式列舉 | |
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>
為:
- 如果有從
A
到B
的識別轉換或隱含轉換,則為隱含可為 Null 的轉換; - 如果存在從
A
到B
的顯式轉換,那麼這是顯式的可為 Null 轉換。 - 否則為無效。
從 Nullable<A>
轉換成 B
為:
- 在從
A
到B
存在識別轉換或隱含或明確的數值轉換時,即為明確的可為 Null 的轉換。 - 否則為無效。
從 Nullable<A>
轉換成 Nullable<B>
為:
- 如果有從
A
轉換成B
的身分識別轉換,則為身分識別轉換; - 如果存在從
A
到B
的隱含或明確數值轉換,那麼將進行明確的可為 Null 的轉換。 - 否則為無效。
運營商
預先定義的運算符如下所示。
這些運算子在多載解析期間會根據隱式轉換的一般規則進行考慮 ,前提是至少有一個運算元的類型為 nint
或 nuint
。
每個運算子的 IL 包含不同的情況下,unchecked
和 checked
內容的變體。
單一運算 | 操作員簽章 | 伊利諾伊 |
---|---|---|
+ |
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
是原生 int,會遵循與其他具有預先定義運算符的基本類型相同的規則。
具體來說,表達式會系結為 x = (T)(x op y)
,其中 T
是 x
的類型,其中 x
只會評估一次。
shift 運算子應該遮罩要移位的位數-如果 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
,以指出類型參考的哪些部分是原生 ints。
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
的型別參考編碼涵蓋在 NativeIntegerAttribute.md中。
替代選擇
上述「類型清除」方法的替代方法是引進新的類型:System.NativeInt
和 System.NativeUInt
。
public readonly struct NativeInt
{
public IntPtr Value;
}
獨特的類型允許與 IntPtr
不同的重載,並允許獨特的解析和 ToString()
。
不過,CLR 必須處理更多工作來有效地處理這些類型,這會使此功能失去其主要目的——效率。
使用 IntPtr
的現有原生 int 程式代碼互操作會比較困難。
另一個替代方法是在架構中新增更多針對 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