메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 LDM(언어 디자인 모임) 노트에 기록됩니다.
사양문서에서 기능 사양서를 C# 언어 표준으로 채택하는 프로세스에 대해 더 자세히 알아볼 수 있습니다.
챔피언 이슈: https://github.com/dotnet/csharplang/issues/435
요약
네이티브 크기의 부호 있는 정수 및 부호 없는 정수 형식에 대한 언어 지원입니다.
동기는 인터롭 시나리오 및 저수준 라이브러리를 위한 것입니다.
디자인
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 ] 범위에 있습니다.
MinValue이외의 값들은 상수로 출력되지 않기 때문에, MaxValue나 nint에는 nuint 및 nuint.MinValue 필드가 없습니다.
상수 접기는 모든 단항 연산자 { +, -, ~ } 및 이진 연산자 { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }에 대해 지원됩니다.
상수 접기 작업은 컴파일러 플랫폼에 관계없이 일관된 동작을 위해 네이티브 int가 아닌 Int32 및 UInt32 피연산자를 사용하여 평가됩니다.
연산 결과가 32비트 내의 상수 값인 경우, 컴파일 시에 상수 폴딩이 수행됩니다.
그렇지 않으면 작업이 런타임에 실행되고 상수로 간주되지 않습니다.
변환
nint
IntPtr및 nuintUIntPtr간에 ID 변환이 있습니다.
네이티브 int와 기본 형식만 다른 복합 형식 간에, 배열, Nullable<>, 생성된 형식, 그리고 튜플에 대한 동일 변환이 존재합니다.
아래 표에서는 특수 형식 간의 변환을 다룹니다.
(각 변환에 대한 IL에는 unchecked 및 checked 컨텍스트에 대한 변형이 포함됩니다(다른 경우).
아래 표의 일반 참고 사항:
-
conv.u네이티브 정수로의 0 확장 변환이며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 nuintconv.i사용하는 반면byte to nintbyte 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는conv.ovf.i.un가checked void* to long을 사용하는 방식과 동일하게conv.ovf.i8.un을 사용합니다.
| 피연산자 | 타겟 | 변환 | IL(IL) |
|---|---|---|---|
object |
nint |
제품 개봉 | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
암시적 숫자 | conv.i |
byte |
nint |
암시적 숫자 | conv.u |
short |
nint |
암시적 숫자 | conv.i |
ushort |
nint |
암시적 숫자 | conv.u |
int |
nint |
암시적 숫자 | 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 |
암시적 숫자 | 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(IL) |
|---|---|---|---|
nint |
object |
권투 | box |
nint |
void* |
PointerToVoid | 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 |
명시적 숫자 | 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 |
암시적 숫자 | 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* |
PointerToVoid | 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 |
명시적 숫자 | 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(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(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인 경우, 미리 정의된 연산자가 있는 다른 기본 형식과 동일한 규칙을 따릅니다.
특히 이 식은 x = (T)(x op y)이 T 형식이고, x이 한 번만 평가되는 조건에서 x에 바인딩됩니다.
시프트 연산자는 이동할 비트 수를 마스킹해야 합니다. 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 형식의 일부가 아닙니다.
따라서 이러한 변환 및 연산자 사용할 수 없습니다.
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 명시적으로 제외됩니다.
// 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 암시적으로 포함됩니다. .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 암시적으로 포함되며, 기본 형식은 해당 네이티브 정수 형식으로 대체됩니다.
예를 들어 IntPtrISerializable, IEquatable<IntPtr>, IComparable<IntPtr>구현하는 경우 nintISerializable, 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 코드와의 상호 운용이 더 어려울 수 있습니다.
또 다른 대안은 특정 컴파일러 지원 없이 프레임워크에서 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