次の方法で共有


ネイティブ サイズの整数

メモ

この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/435

まとめ

ネイティブ サイズの符号付き整数型と符号なし整数型の言語サポート。

動機は、相互運用シナリオと低レベル ライブラリです。

設計

識別子 nintnuint は、ネイティブの符号付き整数型と符号なし整数型を表す新しいコンテキスト キーワードです。 識別子は、名前検索でそのプログラムの場所で実行可能な結果が見つからない場合にのみキーワードとして扱われます。

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 または nuintMinValue または MaxValue フィールドはありません。

定数の折りたたみは、すべての単項演算子 { +, -, ~ } および二項演算子 { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> } でサポートされています。 定数の折りたたみ操作は、コンパイラ プラットフォームに関係なく一貫した動作を実現するために、ネイティブ int ではなく Int32 および UInt32 オペランドを使用して評価されます。 操作の結果として 32 ビットの定数値が返される場合は、コンパイル時に定数の折りたたみが実行されます。 それ以外の場合、操作は実行時に実行され、定数とは見なされません。

コンバージョン

nintIntPtr の間、および nuintUIntPtr の間には ID 変換があります。 ネイティブ int と基になる型のみが異なる複合型 (配列、Nullable<>、構築型、およびタプル) の間では、同一性変換が行われます。

次の表では、特殊な型間の変換について説明しています。 (各変換の IL には、uncheckedchecked コンテキストのバリアントが含まれます (異なる場合)。)

次の表の一般的な注意事項:

  • 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 nintsbyte to nuintconv.i を使用し、byte to nintbyte to nuintconv.u を使用します。すべては であるため、を拡大しています。
  • nint to bytenuint to byte では conv.u1 を使用するのに対して、nint to sbytenuint to sbyte では conv.i1 を使用します。 bytesbyteshort、およびushortの「スタックの種類」はint32です。 そのため、conv.i1 は実質的に "符号付きバイトにダウンキャストしてから int32 まで符号拡張する" 一方、conv.u1 は実質的に "符号なしバイトにダウンキャストしてから int32 までゼロ拡張" します。
  • checked void* to nint では checked void* to longconv.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 には、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
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) としてバインドされます。Tx の型であり、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 値が 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 型に変換されます。または、もう一方のオペランドが sbyteshortintnint または long 型の場合はバインディング時エラーが発生します。
  • いずれかのオペランドが nuint 型の場合、もう一方のオペランドは nuint 型に変換されます。または、もう一方のオペランドが sbyteshortintnint または long 型の場合はバインディング時エラーが発生します。
  • あるいは、どちらかのオペランドが long 型の場合、もう一方のオペランドは long 型に変換されます。
  • それ以外の場合、いずれかのオペランドが uint 型で、もう一方のオペランドが型 sbyteshortnint または 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.IntPtrSystem.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.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.IntPtr および System.UIntPtr によって実装されたインターフェイスは、nint および nuint暗黙的に含まれています。基になる型の出現は、対応するネイティブ整数型に置き換えられます。 たとえば、IntPtrISerializable, IEquatable<IntPtr>, IComparable<IntPtr> を実装する場合、nintISerializable, IEquatable<nint>, IComparable<nint> を実装します。

オーバーライド、非表示、実装

nintSystem.IntPtr、および nuintSystem.UIntPtr は、オーバーライド、非表示、実装と同等と見なされます。

オーバーロードは、nintSystem.IntPtr、および nuintSystem.UIntPtr だけで異なることはできません。 オーバーライドと実装は、nintSystem.IntPtr、または nuintSystem.UIntPtr でそれぞれ異なる場合があります。 メソッドは、nintSystem.IntPtr、または nuintSystem.UIntPtr だけで異なる他のメソッドを非表示にします。

その他

配列インデックスとして使用される nint 式と nuint 式は変換なしで出力されます。

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 に対してアトミックです。

フィールドは 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 に関わる型参照についてコンパイラ診断は、nintnuintではなく、IntPtr または UIntPtr を報告します。

メタデータ

nintnuint はメタデータで 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.NativeIntSystem.NativeUInt) を導入します。

public readonly struct NativeInt
{
    public IntPtr Value;
}

固有の型を使用すると、IntPtr とは異なるオーバーロードが可能になり、個別の解析と ToString() が可能になります。 ただし、CLR でこれらの型を効率的に処理する作業が増え、機能の主な目的である効率が低下します。 また、IntPtr を使用する既存のネイティブ int コードとの相互運用がより困難になります。

もう 1 つの方法は、フレームワーク内の IntPtr に対するネイティブな int サポートを追加することですが、特定のコンパイラのサポートはありません。 新しい変換と算術演算は、コンパイラによって自動的にサポートされます。 ただし、言語はキーワード、定数、または checked 操作を提供しません。

デザインに関する会議