ネイティブ サイズの整数
メモ
この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の 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
で表され、コンパイラによってネイティブの int としてその型に対する追加の変換や操作が行われます。
定数
定数式は nint
型または nuint
型にすることができます。
ネイティブ int リテラルの直接構文はありません。 代わりに、他の整数定数値の暗黙的または明示的なキャストを使用できます: const nint i = (nint)42;
。
nint
定数は [int.MinValue
, int.MaxValue
] の範囲内です。
nuint
定数は [uint.MinValue
, uint.MaxValue
] の範囲内です。
nuint.MinValue
以外はこれらの値を定数として出力できないため、nint
または nuint
に MinValue
または MaxValue
フィールドはありません。
定数の折りたたみは、すべての単項演算子 { +
, -
, ~
} および二項演算子 { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
} でサポートされています。
定数の折りたたみ操作は、コンパイラ プラットフォームに関係なく一貫した動作を実現するために、ネイティブ int ではなく Int32
および UInt32
オペランドを使用して評価されます。
操作の結果として 32 ビットの定数値が返される場合は、コンパイル時に定数の折りたたみが実行されます。
それ以外の場合、操作は実行時に実行され、定数とは見なされません。
コンバージョン
nint
と IntPtr
の間、および nuint
と UIntPtr
の間には ID 変換があります。
ネイティブ int と基になる型のみが異なる複合型 (配列、Nullable<>
、構築型、およびタプル) の間では、同一性変換が行われます。
次の表では、特殊な型間の変換について説明しています。
(各変換の 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
ではchecked void* to long
がconv.ovf.i8.un
を使用するのと同じ方法でconv.ovf.i.un
を使用します。
オペランド | Target | 変換 | 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 |
ID | |
UIntPtr |
nint |
None | |
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 |
None | |
UIntPtr |
nuint |
ID | |
列挙 | nint |
ExplicitEnumeration | |
列挙 | nuint |
ExplicitEnumeration |
オペランド | Target | 変換 | 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 |
ID | |
nint |
UIntPtr |
None | |
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 |
None | |
nuint |
UIntPtr |
ID | |
nuint |
列挙 | ExplicitEnumeration |
A
から Nullable<B>
への変換は次のとおりです。
- ID 変換または
A
からB
への暗黙的な変換がある場合は、暗黙的な null 許容変換。 A
からB
への明示的な変換がある場合は、明示的な null 許容変換。- それ以外の場合は無効です。
Nullable<A>
から B
への変換は次のとおりです。
- ID 変換、または
A
からB
への暗黙的または明示的な数値変換がある場合は、明示的な null 許容変換。 - それ以外の場合は無効です。
Nullable<A>
から Nullable<B>
への変換は次のとおりです。
A
からB
への ID 変換がある場合の ID 変換。A
からB
への暗黙的または明示的な数値変換がある場合は、明示的な null 許容変換。- それ以外の場合は無効です。
Operators
定義済み演算子は次のとおりです。
これらの演算子は、オーバーロードの解決中に、少なくとも 1 つのオペランドの型が 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 |
Binary | 演算子の署名 | 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
または y
がネイティブ int である複合代入演算 x op= y
では、定義済みの演算子を持つ他のプリミティブ型と同じルールに従います。
具体的には、式は x = (T)(x op y)
としてバインドされます。T
は x
の型であり、x
は 1 回だけ評価されます。
シフト演算子はシフトするビット数をマスクする必要があります。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
に対してアトミックです。
フィールドは nint
型と nuint
型の volatile
としてマークできます。
ただし、ECMA-334 15.5.4 には、基本タイプ System.IntPtr
または System.UIntPtr
を持つ enum
は含まれません。
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)
は IntPtr.Size
ではなく sizeof(IntPtr)
として実装されます。sizeof(nuint)
は UIntPtr.Size
ではなく sizeof(UIntPtr)
として実装されます。
nint
または nuint
に関わる型参照についてコンパイラ診断は、nint
や nuint
ではなく、IntPtr
または UIntPtr
を報告します。
メタデータ
nint
と nuint
はメタデータで System.IntPtr
および System.UIntPtr
として表されます。
nint
または nuint
を含む型参照は、型参照のどの部分がネイティブ int であるかを示す 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
を使用した型参照のエンコードについては、NativeIntegerAttribute.md で説明しています。
代替
上記の "型消去" アプローチの代わりに、新しい型 (System.NativeInt
と System.NativeUInt
) を導入します。
public readonly struct NativeInt
{
public IntPtr Value;
}
固有の型を使用すると、IntPtr
とは異なるオーバーロードが可能になり、個別の解析と ToString()
が可能になります。
ただし、CLR でこれらの型を効率的に処理する作業が増え、機能の主な目的である効率が低下します。
また、IntPtr
を使用する既存のネイティブ int コードとの相互運用がより困難になります。
もう 1 つの方法は、フレームワーク内の IntPtr
に対するネイティブな int サポートを追加することですが、特定のコンパイラのサポートはありません。
新しい変換と算術演算は、コンパイラによって自動的にサポートされます。
ただし、言語はキーワード、定数、または 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
C# feature specifications