次の方法で共有


ネイティブ サイズの整数

手記

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

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

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

概要

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

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

設計

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] の範囲内です。

nint または nuintMinValue または MaxValue フィールドはありません。nuint.MinValue以外は、これらの値を定数として出力できないためです。

定数折りたたみは、すべての単項演算子 { +-~} および二項演算子 { +-*/%==!=<<=>>=&|^<<>> } でサポートされています。 定数フォールディング操作は、コンパイラ プラットフォームに関係なく一貫した動作を実現するために、ネイティブ 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 コンテキストは次のとおりです。
    • signed to *conv.i* (ここで * は目標幅を指します)
    • unsigned to *conv.u* (ここで * は目標幅を指します)
  • 縮小unchecked コンテキストは次のとおりです。
    • any to signed *conv.i* (ここで * は目標幅を指します)
    • any to unsigned *conv.u* (ここで * は目標幅を指します)

いくつかの例を取る:

  • sbyte to nintsbyte to nuintconv.i を使用し、byte to nintbyte to nuintconv.u を使用します。すべては であるため、を拡大しています。
  • nint to bytenuint to byteconv.u1 を使用しながら、nint to sbytenuint to sbyteconv.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 を使用します。
オペランド ターゲット 変換 イリノイ
object nint 開封の儀 unbox
void* nint ポインタートゥボイド (PointerToVoid) nop / conv.ovf.i.un
sbyte nint ImplicitNumeric conv.i
byte nint インプリシットニューメリック 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 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 インプリシット・ヌメリック 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 同一性
列挙 nint ExplicitEnumeration
列挙 nuint ExplicitEnumeration
オペランド ターゲット 変換 イリノイ
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 エクスプリシット・ナメリック 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 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 同一性
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 つのオペランドが 型または の場合に 暗黙的な変換の通常の規則に基づいて考慮されます。

(各演算子の IL には、uncheckedchecked コンテキストのバリアントが含まれます (異なる場合)。

単項演算子 演算子の署名 イリノイ
+ 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
バイナリ 演算子の署名 イリノイ
+ 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= yx または y がネイティブ int である場合は、定義済みの演算子を持つ他のプリミティブ型と同じ規則に従います。 具体的には、式は、Tx の型であり、x が 1 回だけ評価される x = (T)(x op y) としてバインドされます。

シフト演算子は、シフトするビット数をマスクする必要があります。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に変換されます。または、もう一方のオペランドが型 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によって実装されるインターフェイスは、nintnuintに暗黙的に 含まれ、基になる型が対応するネイティブ整数型に置き換えられます。 たとえば、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 に関わる型参照についてコンパイラ診断は、IntPtrUIntPtrではなく、nint または nuint を報告します。

メタデータ

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 操作を提供しません。

デザインに関する会議