네이티브 크기의 정수
메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 LDM(언어 디자인 모임) 노트에 기록됩니다.
사양문서에서 기능 사양서를 C# 언어 표준으로 채택하는 프로세스에 대해 더 자세히 알아볼 수 있습니다.
요약
네이티브 크기의 부호 있는 정수 및 부호 없는 정수 형식에 대한 언어 지원입니다.
동기는 인터롭 시나리오 및 저수준 라이브러리를 위한 것입니다.
디자인
nint
및 nuint
식별자는 네이티브 부호 있는 정수 및 부호 없는 정수 형식을 나타내는 새로운 컨텍스트 키워드입니다.
식별자는 이름 조회가 해당 프로그램 위치에서 실행 가능한 결과를 찾지 못하는 경우에만 키워드로 처리됩니다.
nint x = 3;
_ = nint.Equals(x, 3);
nint
및 nuint
형식은 해당 형식에 대한 추가 변환 및 연산을 네이티브 ints로 표시하는 컴파일러와 함께 System.IntPtr
및 System.UIntPtr
기본 형식으로 표시됩니다.
상수
상수 표현식은 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
네이티브 정수로의 0 확장 변환이며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까지 0 확장"하는 것입니다. -
checked void* to nint
는checked void* to long
가conv.ovf.i8.un
을 사용하는 방식과 동일하게conv.ovf.i.un
을 사용합니다.
피연산자 | 타겟 | 변환 | IL(IL) |
---|---|---|---|
object |
nint |
제품 개봉 | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
암시적 숫자 | conv.i |
byte |
nint |
ImplicitNumeric | conv.u |
short |
nint |
ImplicitNumeric | conv.i |
ushort |
nint |
암묵적 숫자 | conv.u |
int |
nint |
ImplicitNumeric | conv.i |
uint |
nint |
명시적 숫자 | conv.u / conv.ovf.i.un |
long |
nint |
명시적 숫자 | conv.i / conv.ovf.i |
ulong |
nint |
명시적 숫자 | conv.i / conv.ovf.i.un |
char |
nint |
ImplicitNumeric | conv.u |
float |
nint |
명시적 수치 | conv.i / conv.ovf.i |
double |
nint |
명시적 숫자 | conv.i / conv.ovf.i |
decimal |
nint |
명시적 수치 | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
신원 | |
UIntPtr |
nint |
없음 | |
object |
nuint |
언박싱 | unbox |
void* |
nuint |
PointerToVoid | nop |
sbyte |
nuint |
명시적 숫자 | conv.i / conv.ovf.u |
byte |
nuint |
암시적 숫자 | conv.u |
short |
nuint |
엑스플리싯누메릭 | conv.i / conv.ovf.u |
ushort |
nuint |
암시적 숫자 | conv.u |
int |
nuint |
명시적 숫자 | conv.i / conv.ovf.u |
uint |
nuint |
암묵적 수치 | conv.u |
long |
nuint |
명시적 숫자 | conv.u / conv.ovf.u |
ulong |
nuint |
명시적 수치 | conv.u / conv.ovf.u.un |
char |
nuint |
암시적 숫자 | conv.u |
float |
nuint |
명시적 수치 | conv.u / conv.ovf.u |
double |
nuint |
명시적 숫자 | conv.u / conv.ovf.u |
decimal |
nuint |
명시적 수치 | 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 |
명시적숫자 |
conv.u (생략 가능) / conv.ovf.u |
nint |
sbyte |
명시적 숫자 | conv.i1 / conv.ovf.i1 |
nint |
byte |
명시적 숫자 | conv.u1 / conv.ovf.u1 |
nint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2 |
nint |
ushort |
명시적 수치 | conv.u2 / conv.ovf.u2 |
nint |
int |
명시적 숫자 | conv.i4 / conv.ovf.i4 |
nint |
uint |
명시적 숫자 | conv.u4 / conv.ovf.u4 |
nint |
long |
ImplicitNumeric | conv.i8 |
nint |
ulong |
명시적 숫자 | conv.i8 / conv.ovf.u8 |
nint |
char |
명시적 숫자 | conv.u2 / conv.ovf.u2 |
nint |
float |
암시적 숫자 | conv.r4 |
nint |
double |
암시적 숫자 | conv.r8 |
nint |
decimal |
암묵적 숫자 | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
신원 | |
nint |
UIntPtr |
없음 | |
nint |
열거 | 명시적 열거 | |
nuint |
object |
권투 | box |
nuint |
void* |
포인터 투 보이드 | nop |
nuint |
nint |
명시적 숫자 |
conv.i (생략 가능) / conv.ovf.i.un |
nuint |
sbyte |
명시적 수치 | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
명시적 숫자 | conv.u1 / conv.ovf.u1.un |
nuint |
short |
ExplicitNumeric | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
명시적 숫자 | conv.u2 / conv.ovf.u2.un |
nuint |
int |
명시적 숫자 | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
명시적 숫자 | conv.u4 / conv.ovf.u4.un |
nuint |
long |
명시적 숫자 | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
암시적 숫자 | conv.u8 |
nuint |
char |
명시적숫자 | conv.u2 / conv.ovf.u2.un |
nuint |
float |
암묵적 수치 | conv.r.un conv.r4 |
nuint |
double |
암시적 숫자 | conv.r.un conv.r8 |
nuint |
decimal |
암시적수치 | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
없음 | |
nuint |
UIntPtr |
신원 | |
nuint |
열거 | 명시적 열거 |
A
에서 Nullable<B>
로의 변환은 다음과 같습니다.
- id 변환 또는
A
에서B
로의 암시적 변환이 있을 때, 암시적 nullable 변환이 존재합니다. -
A
에서B
로의 명시적 변환이 있는 경우 명시적 nullable 변환 - 그렇지 않으면 유효하지 않습니다.
Nullable<A>
에서 B
로의 변환은 다음과 같습니다.
-
A
에서B
로 ID 변환이 있거나 암시적 또는 명시적 숫자 변환이 있는 경우 명시적 nullable 변환 - 그렇지 않으면 유효하지 않습니다.
Nullable<A>
에서 Nullable<B>
로의 변환 결과는 다음과 같습니다.
-
A
에서B
으로의 id 변환이 있다면; -
A
에서B
로 암시적 또는 명시적 숫자 변환이 있는 경우 명시적으로 널러블로 변환합니다. - 그렇지 않으면 유효하지 않습니다.
연산자
미리 정의된 연산자는 다음과 같습니다.
이러한 연산자는 피연산자 중 하나 이상이
(각 연산자에 대한 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 |
바이너리 | 연산자 서명 | 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인 경우, 미리 정의된 연산자가 있는 다른 기본 형식과 동일한 규칙을 따릅니다.
특히 이 식은 T
이 x
형식이고, x
이 한 번만 평가되는 조건에서 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
형식으로 변환되거나, 다른 피연산자 형식이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)
구현됩니다.
컴파일러 진단은 IntPtr
또는 UIntPtr
가 아닌 nint
또는 nuint
과 관련된 형식 참조에 대해 nint
또는 nuint
을 보고합니다.
메타데이터
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 코드와의 상호 운용이 더 어려울 수 있습니다.
또 다른 대안은 특정 컴파일러 지원 없이 프레임워크에서 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