メモ
この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の 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] の範囲内です。
MinValue 以外はこれらの値を定数として出力できないため、MaxValue または nint に nuint または nuint.MinValue フィールドはありません。
定数の折りたたみは、すべての単項演算子 { +, -, ~ } および二項演算子 { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> } でサポートされています。
定数の折りたたみ操作は、コンパイラ プラットフォームに関係なく一貫した動作を実現するために、ネイティブ int ではなく Int32 および UInt32 オペランドを使用して評価されます。
操作の結果として 32 ビットの定数値が返される場合は、コンパイル時に定数の折りたたみが実行されます。
それ以外の場合、操作は実行時に実行され、定数とは見なされません。
コンバージョン
nint と IntPtr の間、および nuint と UIntPtr の間には ID 変換があります。
ネイティブ int と基になる型のみが異なる複合型 (配列、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を使用します。
| オペランド | 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 op= y または x がネイティブ int である複合代入演算 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 に対してアトミックです。
フィールドは 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 を含む型参照は、型参照のどの部分がネイティブ 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