Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Problem z czempionem: https://github.com/dotnet/csharplang/issues/435
Streszczenie
Obsługa języka dla typów liczb całkowitych o rozmiarze natywnym i bez znaku.
Motywacją są scenariusze interoperacyjne i biblioteki niskopoziomowe.
Projektowanie
Identyfikatory nint
i nuint
to nowe kontekstowe słowa kluczowe reprezentujące natywne typy liczb całkowitych ze znakiem natywnym i bez znaku.
Identyfikatory są traktowane tylko jako słowa kluczowe, gdy wyszukiwanie nazw nie znajdzie realnego wyniku w tej lokalizacji programu.
nint x = 3;
_ = nint.Equals(x, 3);
Typy nint
i nuint
są reprezentowane przez podstawowe typy System.IntPtr
i System.UIntPtr
, przy czym kompilator automatycznie zapewnia dodatkowe konwersje i operacje dla tych typów jako natywne liczby całkowite.
Stałe
Wyrażenia stałe mogą być typu nint
lub nuint
.
Nie ma bezpośredniej składni dla natywnych literałów int. Zamiast tego można użyć niejawnych lub jawnych rzutów innych wartości stałych całkowitych: const nint i = (nint)42;
.
Stała nint
znajduje się w zakresie [int.MinValue
, int.MaxValue
].
Stała nuint
znajduje się w zakresie [uint.MinValue
, uint.MaxValue
].
W MinValue
lub MaxValue
nie ma żadnych pól nint
ani nuint
, ponieważ inne niż nuint.MinValue
wartości te nie mogą być emitowane jako stałe.
Stałe składanie jest obsługiwane dla wszystkich operatorów jednoargumentowych { +
, -
, ~
} i operatorów binarnych { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Stałe operacje składania są oceniane przy użyciu Int32
i UInt32
operandów, a nie natywnych ints pod kątem spójnego zachowania niezależnie od platformy kompilatora.
Jeśli operacja powoduje stałą wartość w 32-bitach, składanie stałe jest wykonywane w czasie kompilacji.
W przeciwnym razie operacja jest wykonywana podczas działania programu i nie jest uznawana za stałą.
Konwersje
Istnieje konwersja tożsamości zarówno między nint
a IntPtr
, jak i między nuint
a UIntPtr
.
Istnieje konwersja tożsamości między typami złożonymi, które różnią się jedynie natywnymi liczbami całkowitymi i typami bazowymi: tablice, Nullable<>
, typy skonstruowane i krotki.
W poniższych tabelach omówiono konwersje między typami specjalnymi.
(Il dla każdej konwersji obejmuje warianty dla kontekstów unchecked
i checked
, jeśli są różne).
Uwagi ogólne w poniższej tabeli:
-
conv.u
jest konwersją rozszerzającą zero na natywną liczbę całkowitą, aconv.i
jest konwersją rozszerzającą znak na natywną liczbę całkowitą. -
checked
konteksty dla rozszerzania i zawężania:-
conv.ovf.*
dlasigned to *
-
conv.ovf.*.un
dlaunsigned to *
-
-
unchecked
konteksty rozszerzania są następujące:-
conv.i*
dlasigned to *
(gdzie * jest szerokością docelową) -
conv.u*
dlaunsigned to *
(gdzie * jest szerokością docelową)
-
-
unchecked
konteksty dla zawężania to są:-
conv.i*
dlaany to signed *
(gdzie * jest szerokością docelową) -
conv.u*
dlaany to unsigned *
(gdzie * jest szerokością docelową)
-
Kilka przykładów:
-
sbyte to nint
isbyte to nuint
używająconv.i
, podczas gdybyte to nint
ibyte to nuint
używająconv.u
, ponieważ wszystkie rozszerzają. -
nint to byte
inuint to byte
używająconv.u1
, anint to sbyte
inuint to sbyte
używająconv.i1
. W przypadkubyte
,sbyte
,short
iushort
typ stosu jestint32
. Dlategoconv.i1
jest skutecznie "rzutowany w dół na bajt ze znakiem, a następnie rozszerzony do int32 ze znakiem", podczas gdyconv.u1
jest skutecznie "rzutowany w dół na bajt bez znaku, a następnie rozszerzony do int32 bez znaku". -
checked void* to nint
używaconv.ovf.i.un
w taki sam sposób, w jakichecked void* to long
używaconv.ovf.i8.un
.
Operand | Cel | Konwersja | IL |
---|---|---|---|
object |
nint |
Rozpakowywanie | unbox |
void* |
nint |
WskaźnikToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
Niejawnenumeryczne | conv.i |
byte |
nint |
Niejawnenumeryczne | conv.u |
short |
nint |
Niejawnenumeryczne | conv.i |
ushort |
nint |
Niejawnenumeryczne | conv.u |
int |
nint |
Niejawnenumeryczne | conv.i |
uint |
nint |
JawnaLiczba | conv.u / conv.ovf.i.un |
long |
nint |
JawnaLiczba | conv.i / conv.ovf.i |
ulong |
nint |
JawnaLiczba | conv.i / conv.ovf.i.un |
char |
nint |
Niejawnenumeryczne | conv.u |
float |
nint |
JawnaLiczba | conv.i / conv.ovf.i |
double |
nint |
JawnaLiczba | conv.i / conv.ovf.i |
decimal |
nint |
JawnaLiczba | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
Tożsamość | |
UIntPtr |
nint |
Żaden | |
object |
nuint |
Rozpakowywanie | unbox |
void* |
nuint |
WskaźnikToVoid | Nop |
sbyte |
nuint |
JawnaLiczba | conv.i / conv.ovf.u |
byte |
nuint |
Niejawnenumeryczne | conv.u |
short |
nuint |
JawnaLiczba | conv.i / conv.ovf.u |
ushort |
nuint |
Niejawnenumeryczne | conv.u |
int |
nuint |
JawnaLiczba | conv.i / conv.ovf.u |
uint |
nuint |
Niejawnenumeryczne | conv.u |
long |
nuint |
JawnaLiczba | conv.u / conv.ovf.u |
ulong |
nuint |
JawnaLiczba | conv.u / conv.ovf.u.un |
char |
nuint |
Niejawnenumeryczne | conv.u |
float |
nuint |
JawnaLiczba | conv.u / conv.ovf.u |
double |
nuint |
JawnaLiczba | conv.u / conv.ovf.u |
decimal |
nuint |
JawnaLiczba | ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un |
IntPtr |
nuint |
Żaden | |
UIntPtr |
nuint |
Tożsamość | |
Wyliczenie | nint |
Enumeracja jawna | |
Wyliczenie | nuint |
Enumeracja jawna |
Operand | Cel | Konwersja | IL |
---|---|---|---|
nint |
object |
Boks | box |
nint |
void* |
WskaźnikToVoid | nop / conv.ovf.u |
nint |
nuint |
JawnaLiczba |
conv.u (można pominąć) / conv.ovf.u |
nint |
sbyte |
JawnaLiczba | conv.i1 / conv.ovf.i1 |
nint |
byte |
JawnaLiczba | conv.u1 / conv.ovf.u1 |
nint |
short |
JawnaLiczba | conv.i2 / conv.ovf.i2 |
nint |
ushort |
JawnaLiczba | conv.u2 / conv.ovf.u2 |
nint |
int |
JawnaLiczba | conv.i4 / conv.ovf.i4 |
nint |
uint |
JawnaLiczba | conv.u4 / conv.ovf.u4 |
nint |
long |
Niejawnenumeryczne | conv.i8 |
nint |
ulong |
JawnaLiczba | conv.i8 / conv.ovf.u8 |
nint |
char |
JawnaLiczba | conv.u2 / conv.ovf.u2 |
nint |
float |
Niejawnenumeryczne | conv.r4 |
nint |
double |
Niejawnenumeryczne | conv.r8 |
nint |
decimal |
Niejawnenumeryczne | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Tożsamość | |
nint |
UIntPtr |
Żaden | |
nint |
Wyliczenie | Enumeracja jawna | |
nuint |
object |
Boks | box |
nuint |
void* |
WskaźnikToVoid | Nop |
nuint |
nint |
JawnaLiczba |
conv.i (można pominąć) / conv.ovf.i.un |
nuint |
sbyte |
JawnaLiczba | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
JawnaLiczba | conv.u1 / conv.ovf.u1.un |
nuint |
short |
JawnaLiczba | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
JawnaLiczba | conv.u2 / conv.ovf.u2.un |
nuint |
int |
JawnaLiczba | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
JawnaLiczba | conv.u4 / conv.ovf.u4.un |
nuint |
long |
JawnaLiczba | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
Niejawnenumeryczne | conv.u8 |
nuint |
char |
JawnaLiczba | conv.u2 / conv.ovf.u2.un |
nuint |
float |
Niejawnenumeryczne | conv.r.un conv.r4 |
nuint |
double |
Niejawnenumeryczne | conv.r.un conv.r8 |
nuint |
decimal |
Niejawnenumeryczne | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Żaden | |
nuint |
UIntPtr |
Tożsamość | |
nuint |
Wyliczenie | Enumeracja jawna |
Konwersja z A
na Nullable<B>
to:
- niejawna konwersja na typ dopuszczający wartość null, jeśli istnieje konwersja tożsamości lub niejawna konwersja z
A
doB
; - jawna konwersja do wartości null, jeśli istnieje jawna konwersja z
A
naB
; - w przeciwnym razie jest to nieprawidłowe.
Konwersja z Nullable<A>
na B
to:
- jawna konwersja do typu akceptującego wartość null, jeśli istnieje konwersja tożsamości lub niejawna lub jawna konwersja liczbowa z
A
naB
; - w przeciwnym razie jest to nieprawidłowe.
Konwersja z Nullable<A>
na Nullable<B>
to:
- konwersja tożsamości w przypadku konwersji tożsamości z
A
naB
; - Jawna konwersja na wartość null, jeżeli istnieje niejawna lub jawna konwersja liczbowa z
A
naB
; - w przeciwnym razie jest to nieprawidłowe.
Operatorów
Wstępnie zdefiniowane operatory są następujące.
Te operatory są brane pod uwagę podczas rozpoznawania przeciążeń na podstawie normalnych reguł konwersji niejawnych , jeśli co najmniej jeden z operandów jest typu nint
lub nuint
.
(Il dla każdego operatora zawiera warianty dla unchecked
i kontekstów checked
, jeśli są inne).
Jednoargumentowy | Podpis operatora | 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 |
Dwójkowy | Podpis operatora | 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 |
W przypadku niektórych operatorów binarnych operatory IL wspierają dodatkowe typy operandów (zobacz ECMA-335 III.1.5 Tabela typów operandów). Jednak zestaw typów operandów obsługiwanych przez język C# jest ograniczony dla uproszczenia i spójności z istniejącymi operatorami w języku.
Podnoszone wersje operatorów, gdzie typy argumentów i zwracanych danych to nint?
i nuint?
, są obsługiwane.
Operacje przypisania złożonego x op= y
, w których x
lub y
są natywnymi typami int, podlegają tym samym regułom co inne typy pierwotne ze wstępnie zdefiniowanymi operatorami.
W szczególności wyrażenie jest powiązane jako x = (T)(x op y)
, gdzie T
jest typem x
i gdzie x
jest obliczany tylko raz.
Operatory przesunięcia powinny maskować liczbę bitów do przesunięcia - do 5 bitów, jeśli sizeof(nint)
wynosi 4, i do 6 bitów, jeśli sizeof(nint)
wynosi 8.
(patrz §12.11) w specyfikacji C#).
Kompilator języka C#9 będzie zgłaszać błędy powiązania ze wstępnie zdefiniowanymi operatorami liczb całkowitych natywnych podczas kompilowania przy użyciu starszej wersji języka, ale umożliwi użycie wstępnie zdefiniowanych konwersji do i z natywnych liczb całkowitych.
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
}
}
Arytmetyka wskaźnika
Nie ma wstępnie zdefiniowanych operatorów w języku C# do dodawania lub odejmowania wskaźników z natywnymi przesunięciami liczb całkowitych.
Zamiast tego wartości nint
i nuint
są promowane do long
i ulong
, a arytmetyka wskaźnika używa wstępnie zdefiniowanych operatorów dla tych typów.
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)
Promocje numeryczne binarne
binarne promocje liczbowe tekst informacyjny (zobacz §12.4.7.3) w specyfikacji C#) jest aktualizowany w następujący sposób:
- …
- W przeciwnym razie, jeśli którykolwiek z operandów ma typ
ulong
, drugi operand jest konwertowany na typulong
, albo występuje błąd czasu powiązania, jeśli inny operand jest typusbyte
,short
,int
,nint
lublong
.- W przeciwnym razie, jeśli którykolwiek z operandów ma typ
nuint
, drugi operand jest konwertowany na typnuint
, lub pojawia się błąd związany z czasem powiązania, jeśli drugi operand jest typusbyte
,short
,int
,nint
lublong
.- W przeciwnym razie jeśli którykolwiek operand ma typ
long
, drugi operand jest konwertowany na typlong
.- W przeciwnym razie jeśli operand ma typ
uint
, a drugi operand ma typsbyte
,short
,nint
, lubint
, oba operandy są konwertowane na typlong
.- W przeciwnym razie jeśli którykolwiek operand ma typ
uint
, drugi operand jest konwertowany na typuint
.- W przeciwnym razie, jeśli którykolwiek operand ma typ
nint
, drugi operand jest konwertowany na typnint
.- W przeciwnym razie oba operandy są konwertowane na typ
int
.
Dynamiczny
Konwersje i operatory są syntetyzowane przez kompilator i nie są częścią podstawowych typów IntPtr
i UIntPtr
.
W rezultacie te konwersje i operatory nie są dostępne z powiązania środowiska uruchomieniowego dla 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'
Składowe typu
Jedynym konstruktorem nint
lub nuint
jest konstruktor bez parametrów.
Następujący członkowie System.IntPtr
i System.UIntPtr
są jawnie wykluczeni z nint
lub 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();
Pozostali członkowie System.IntPtr
i System.UIntPtr
są domyślnie uwzględniani w nint
i nuint
. W przypadku programu .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Interfejsy implementowane przez System.IntPtr
i System.UIntPtr
są niejawnie uwzględniane w nint
i nuint
, zastępując wystąpienia typów bazowych odpowiednimi natywnymi typami liczbowymi.
Jeśli na przykład IntPtr
implementuje ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
, nint
implementuje ISerializable, IEquatable<nint>, IComparable<nint>
.
Zastępowanie, ukrywanie i implementowanie
nint
i System.IntPtr
oraz nuint
i System.UIntPtr
są uważane za równoważne w kontekście zastępowania, ukrywania i implementacji.
Przeciążenia nie mogą różnić się jedynie przez nint
i System.IntPtr
oraz nuint
i System.UIntPtr
.
Przesłonięcia i implementacje mogą różnić się samodzielnie między nint
i System.IntPtr
lub nuint
i System.UIntPtr
.
Metody ukrywają inne metody, które różnią się wyłącznie według nint
i System.IntPtr
, lub nuint
i System.UIntPtr
.
Różne
nint
i wyrażenia nuint
używane jako indeksy tablicowe są emitowane bez konwersji.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
i nuint
nie można używać jako typu podstawowego enum
z języka C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Operacje odczytu i zapisu są atomowe dla nint
i nuint
.
Pola mogą być oznaczone volatile
dla typów nint
i nuint
.
ECMA-334 15.5.4 nie obejmuje jednak enum
z bazowym typem System.IntPtr
lub System.UIntPtr
.
default(nint)
i new nint()
są równoważne (nint)0
; default(nuint)
i new nuint()
są równoważne (nuint)0
.
typeof(nint)
jest typeof(IntPtr)
; typeof(nuint)
jest typeof(UIntPtr)
.
sizeof(nint)
i sizeof(nuint)
są obsługiwane, ale wymagają kompilowania w niebezpiecznym kontekście (zgodnie z wymaganiami sizeof(IntPtr)
i sizeof(UIntPtr)
).
Wartości nie są stałymi czasu kompilacji.
sizeof(nint)
jest implementowany jako sizeof(IntPtr)
, a nie IntPtr.Size
; sizeof(nuint)
jest implementowany jako sizeof(UIntPtr)
, a nie UIntPtr.Size
.
Diagnostyka kompilatora dotycząca odwołań do typów, które obejmują nint
lub nuint
, raportują nint
lub nuint
, a nie IntPtr
lub UIntPtr
.
Metadane
nint
i nuint
są reprezentowane w metadanych jako System.IntPtr
i System.UIntPtr
.
Odwołania do typów, które obejmują nint
lub nuint
, są emitowane z System.Runtime.CompilerServices.NativeIntegerAttribute
, aby wskazać, które części odwołania typu są natywne jako liczby całkowite.
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;
}
}
Kodowanie referencji typów za pomocą NativeIntegerAttribute
zostało omówione w NativeIntegerAttribute.md.
Alternatywy
Alternatywą dla powyższego podejścia "wymazywania typów" jest wprowadzenie nowych typów: System.NativeInt
i System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Różne typy umożliwiałyby przeciążenie odrębne od IntPtr
i umożliwiałyby również odrębną analizę ToString()
.
Ale CLR musiałby wykonać więcej pracy, aby efektywnie obsługiwać te typy, co niweczy podstawowy cel tej funkcji - wydajność.
I współdziałanie z istniejącym natywnym kodem int, który używa IntPtr
byłoby trudniejsze.
Inną alternatywą jest dodanie większej natywnej obsługi int dla IntPtr
w frameworkie, ale bez żadnego konkretnego wsparcia dla kompilatora.
Wszelkie nowe konwersje i operacje arytmetyczne będą obsługiwane automatycznie przez kompilator.
Jednak język nie dostarczał słów kluczowych, stałych ani operacji checked
.
Spotkania projektowe
- 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