Celá čísla v nativní velikosti
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v relevantních poznámkách schůzky návrhu jazyka (LDM) .
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/435
Shrnutí
Jazyková podpora pro typy celých čísel s nativní velikostí, se znaménkem i bez něj.
Motivací jsou scénáře interoperability a knihovny na nízké úrovni.
Design
Identifikátory nint
a nuint
jsou nová kontextová klíčová slova, která představují nativní typy celých čísel se znaménkem a bez znaménka.
Identifikátory jsou považovány pouze za klíčová slova, pokud vyhledávání názvů nenajde v daném umístění programu realizovatelný výsledek.
nint x = 3;
_ = nint.Equals(x, 3);
Typy nint
a nuint
jsou reprezentovány podkladovými typy System.IntPtr
a System.UIntPtr
s tím, že kompilátor poskytuje další převody a operace pro tyto typy jako nativní celočíselné typy.
Konstanty
Konstantní výrazy mohou být typu nint
nebo nuint
.
Pro nativní int literály neexistuje žádná přímá syntaxe. Místo toho lze použít implicitní nebo explicitní přetypování jiných celočíselných konstantních hodnot: const nint i = (nint)42;
.
nint
konstanty jsou v rozsahu [ int.MinValue
, int.MaxValue
].
nuint
konstanty jsou v rozsahu [ uint.MinValue
, uint.MaxValue
].
V MinValue
nebo MaxValue
neexistují žádná pole nint
ani nuint
, protože kromě nuint.MinValue
nelze tyto hodnoty vygenerovat jako konstanty.
Konstantní skládání je podporováno pro všechny unární operátory { +
, -
, ~
} a binární operátory { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Konstantní operace skládání se vyhodnocují pomocí Int32
a UInt32
operandů, nikoli nativních int pro konzistentní chování bez ohledu na platformu kompilátoru.
Pokud má operace za následek konstantní hodnotu v 32bitových bitech, provádí se při kompilaci konstantní skládání.
Jinak se operace provádí za běhu a nepovažuje se za konstantu.
Konverzace
Mezi nint
a IntPtr
a mezi nuint
a UIntPtr
existuje převod identity .
Existuje převod identity mezi složenými typy, které se liší pouze nativními inty a podkladovými typy: pole, Nullable<>
, vytvořené typy a řazené kolekce členů.
Následující tabulky popisují převody mezi speciálními typy.
(IL pro každý převod zahrnuje varianty pro kontexty unchecked
a checked
, pokud se liší.)
Obecné poznámky k následující tabulce:
-
conv.u
je převod na nativní celé číslo s nulovým rozšířením aconv.i
je převod na nativní celé číslo s rozšířením znaménka. -
checked
kontexty pro rozšiřující i zužující jsou:-
conv.ovf.*
prosigned to *
-
conv.ovf.*.un
prounsigned to *
-
-
unchecked
kontexty pro rozšiřující jsou:-
conv.i*
prosigned to *
(kde * je cílová šířka) -
conv.u*
prounsigned to *
(kde * je cílová šířka)
-
- Kontexty pro zúžení
unchecked
jsou:-
conv.i*
proany to signed *
(kde * je cílová šířka) -
conv.u*
proany to unsigned *
(kde * je cílová šířka)
-
Několik příkladů:
-
sbyte to nint
asbyte to nuint
používajíconv.i
, zatímcobyte to nint
abyte to nuint
používajíconv.u
, protože jsou všechny rozšiřující. -
nint to byte
anuint to byte
používajíconv.u1
, zatímconint to sbyte
anuint to sbyte
používajíconv.i1
. Probyte
,sbyte
,short
aushort
je typ zásobníkuint32
. Takžeconv.i1
je efektivně "převeden na podepsaný byte a poté rozšířen na int32", zatímcoconv.u1
je efektivně "převeden na nepodepsaný byte a poté rozšířen s nulovým rozšířením na int32". -
checked void* to nint
používáconv.ovf.i.un
stejným způsobem jakochecked void* to long
používáconv.ovf.i8.un
.
Operand | Cíl | Přeměna | IL |
---|---|---|---|
object |
nint |
Rozbalení | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
Implicitní číslo | conv.i |
byte |
nint |
Implicitní číslo | conv.u |
short |
nint |
Implicitní číslo | conv.i |
ushort |
nint |
Implicitní číslo | conv.u |
int |
nint |
Implicitní číslo | conv.i |
uint |
nint |
Explicitně číselný | conv.u / conv.ovf.i.un |
long |
nint |
Explicitně číselný | conv.i / conv.ovf.i |
ulong |
nint |
Explicitně číselný | conv.i / conv.ovf.i.un |
char |
nint |
Implicitní číslo | conv.u |
float |
nint |
Explicitně číselný | conv.i / conv.ovf.i |
double |
nint |
Explicitně číselný | conv.i / conv.ovf.i |
decimal |
nint |
Explicitně číselný | long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i |
IntPtr |
nint |
Identita | |
UIntPtr |
nint |
Žádný | |
object |
nuint |
Rozbalení | unbox |
void* |
nuint |
PointerToVoid | Nop |
sbyte |
nuint |
Explicitně číselný | conv.i / conv.ovf.u |
byte |
nuint |
Implicitní číslo | conv.u |
short |
nuint |
Explicitně číselný | conv.i / conv.ovf.u |
ushort |
nuint |
Implicitní číslo | conv.u |
int |
nuint |
Explicitně číselný | conv.i / conv.ovf.u |
uint |
nuint |
Implicitní číslo | conv.u |
long |
nuint |
Explicitně číselný | conv.u / conv.ovf.u |
ulong |
nuint |
Explicitně číselný | conv.u / conv.ovf.u.un |
char |
nuint |
Implicitní číslo | conv.u |
float |
nuint |
Explicitně číselný | conv.u / conv.ovf.u |
double |
nuint |
Explicitně číselný | conv.u / conv.ovf.u |
decimal |
nuint |
Explicitně číselný | ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un |
IntPtr |
nuint |
Žádný | |
UIntPtr |
nuint |
Identita | |
Výčet | nint |
ExplicitEnumeration | |
Výčet | nuint |
ExplicitEnumeration |
Operand | Cíl | Přeměna | IL |
---|---|---|---|
nint |
object |
Box | box |
nint |
void* |
PointerToVoid | nop / conv.ovf.u |
nint |
nuint |
Explicitně číselný |
conv.u (lze vynechat) / conv.ovf.u |
nint |
sbyte |
Explicitně číselný | conv.i1 / conv.ovf.i1 |
nint |
byte |
Explicitně číselný | conv.u1 / conv.ovf.u1 |
nint |
short |
Explicitně číselný | conv.i2 / conv.ovf.i2 |
nint |
ushort |
Explicitně číselný | conv.u2 / conv.ovf.u2 |
nint |
int |
Explicitně číselný | conv.i4 / conv.ovf.i4 |
nint |
uint |
Explicitně číselný | conv.u4 / conv.ovf.u4 |
nint |
long |
Implicitní číslo | conv.i8 |
nint |
ulong |
Explicitně číselný | conv.i8 / conv.ovf.u8 |
nint |
char |
Explicitně číselný | conv.u2 / conv.ovf.u2 |
nint |
float |
Implicitní číslo | conv.r4 |
nint |
double |
Implicitní číslo | conv.r8 |
nint |
decimal |
Implicitní číslo | conv.i8 decimal decimal.op_Implicit(long) |
nint |
IntPtr |
Identita | |
nint |
UIntPtr |
Žádný | |
nint |
Výčet | ExplicitEnumeration | |
nuint |
object |
Box | box |
nuint |
void* |
PointerToVoid | Nop |
nuint |
nint |
Explicitně číselný |
conv.i (lze vynechat) / conv.ovf.i.un |
nuint |
sbyte |
Explicitně číselný | conv.i1 / conv.ovf.i1.un |
nuint |
byte |
Explicitně číselný | conv.u1 / conv.ovf.u1.un |
nuint |
short |
Explicitně číselný | conv.i2 / conv.ovf.i2.un |
nuint |
ushort |
Explicitně číselný | conv.u2 / conv.ovf.u2.un |
nuint |
int |
Explicitně číselný | conv.i4 / conv.ovf.i4.un |
nuint |
uint |
Explicitně číselný | conv.u4 / conv.ovf.u4.un |
nuint |
long |
Explicitně číselný | conv.u8 / conv.ovf.i8.un |
nuint |
ulong |
Implicitní číslo | conv.u8 |
nuint |
char |
Explicitně číselný | conv.u2 / conv.ovf.u2.un |
nuint |
float |
Implicitní číslo | conv.r.un conv.r4 |
nuint |
double |
Implicitní číslo | conv.r.un conv.r8 |
nuint |
decimal |
Implicitní číslo | conv.u8 decimal decimal.op_Implicit(ulong) |
nuint |
IntPtr |
Žádný | |
nuint |
UIntPtr |
Identita | |
nuint |
Výčet | ExplicitEnumeration |
Převod z A
na Nullable<B>
je:
- implicitní nulovatelný převod, pokud existuje převod identity nebo implicitní převod z
A
naB
; - explicitní nulovatelný převod, pokud existuje explicitní převod z
A
naB
; - v opačném případě je neplatný.
Převod z Nullable<A>
na B
je:
- explicitní převod s možnou hodnotou null, pokud existuje převod identity nebo implicitní nebo explicitní číselný převod z
A
naB
; - v opačném případě je neplatný.
Převod z Nullable<A>
na Nullable<B>
je:
- převod identity, pokud existuje převod identity z
A
naB
; - explicitní nulový převod, pokud existuje implicitní nebo explicitní číselný převod z
A
naB
; - v opačném případě je neplatný.
Operátoři
Předdefinované operátory jsou následující.
Tyto operátory jsou považovány za přetížené na základě normálních pravidel implicitních převodů , pokud alespoň jeden z operandů je typu nint
nebo nuint
.
(Il pro každého operátora obsahuje varianty pro kontexty unchecked
a checked
, pokud se liší.)
Unární | Podpis operátoru | 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 |
Binární | Podpis operátoru | 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 |
U některých binárních operátorů podporují operátory IL další typy operandů (viz tabulka typů ECMA-335 III.1.5 Operand). Sada typů operandů podporovaných jazykem C# je ale omezená pro jednoduchost a konzistenci s existujícími operátory v jazyce.
Jsou podporovány zdvižené verze operátorů, kde jsou argumenty a návratové typy nint?
a nuint?
.
Operace složeného přiřazení x op= y
, kde x
nebo y
jsou nativní inty, se řídí stejnými pravidly jako u jiných primitivních typů s předem definovanými operátory.
Konkrétně je výraz vázán jako x = (T)(x op y)
, kde T
je typ x
a kde x
je vyhodnocen pouze jednou.
Operátory posunu by měly maskovat počet bitů, které se mají posunout – na 5 bitů, pokud sizeof(nint)
je 4 a 6 bitů, pokud sizeof(nint)
je 8.
(Viz §12.11) ve specifikaci jazyka C#).
Kompilátor C#9 bude hlásit chyby vazby na předdefinované nativní celočíselné operátory při kompilaci s dřívější jazykovou verzí, ale umožní používat předdefinované převody do a z nativních celých čísel.
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
}
}
Ukazatelová aritmetika
V jazyce C# nejsou žádné předdefinované operátory pro sčítání nebo odčítání nativního celočíselného posunu u ukazatele.
Místo toho jsou hodnoty nint
a nuint
povýšeny na long
a ulong
a aritmetické operátory se pro tyto typy používají předdefinovaným způsobem.
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)
Binární číselné povýšení
Informativní text binárních číselných propagací (viz §12.4.7.3ve specifikaci jazyka C#) se aktualizuje následujícím způsobem:
- …
- V opačném případě, je-li jeden operand typu
ulong
, druhý operand je převeden na typulong
, nebo dojde k chybě doby vazby, pokud druhý operand je typusbyte
,short
,int
,nint
nebolong
.- Jinak, pokud je jeden operand typu
nuint
, druhý operand je převeden na typnuint
, nebo dojde k chybě vazby v případě, že druhý operand je typusbyte
,short
,int
,nint
nebolong
.- V opačném případě je-li jeden operand typu
long
, druhý operand je převeden na typlong
.- V opačném případě, je-li jeden operand typu
uint
a druhý operand je typusbyte
,short
,nint
, neboint
, oba operandy jsou převedeny na typlong
.- V opačném případě je-li jeden operand typu
uint
, druhý operand je převeden na typuint
.- Jinak, pokud je některý operand typu
nint
, druhý operand je převeden na typnint
.- V opačném případě jsou oba operandy převedeny na typ
int
.
Dynamický
Převody a operátory jsou syntetizovány kompilátorem a nejsou součástí podkladových IntPtr
a UIntPtr
typů.
V důsledku toho nejsou tyto převody a operátory k dispozici z pořadače modulu runtime pro 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'
Členy typu
Jediný konstruktor pro nint
nebo nuint
je konstruktor bez parametrů.
Z
// 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();
Zbývající členové System.IntPtr
a System.UIntPtr
jsou implicitně zahrnuti do nint
a nuint
. Pro rozhraní .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Rozhraní implementovaná System.IntPtr
a System.UIntPtr
jsou implicitně zahrnuta v nint
a nuint
, přičemž výskyty základních typů jsou nahrazeny odpovídajícími nativními celočíselnými typy.
Pokud například IntPtr
implementuje ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
, nint
implementuje ISerializable, IEquatable<nint>, IComparable<nint>
.
Přepsání, skrytí a implementace
nint
a System.IntPtr
a nuint
a System.UIntPtr
jsou považovány za ekvivalenty pro přepsání, skrytí a implementaci.
Přetížení se nemohou lišit pouze podle nint
a System.IntPtr
, ani pouze podle nuint
a System.UIntPtr
.
Přepsání a implementace se mohou lišit pouze podle nint
a System.IntPtr
, nebo nuint
a System.UIntPtr
.
Metody skrývají jiné metody, které se liší pouze v parametrech nint
a System.IntPtr
, nebo nuint
a System.UIntPtr
.
Různé
Výrazy nint
a nuint
, použité jako indexy pole, se emitují bez převodu.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
a nuint
nelze použít jako základní typ enum
z jazyka C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Čtení a zápisy jsou atomické pro nint
a nuint
.
Pole mohou být označena volatile
pro typy nint
a nuint
.
ECMA-334 15.5.4 nezahrnuje enum
se základním typem System.IntPtr
ani System.UIntPtr
.
default(nint)
a new nint()
jsou ekvivalentní (nint)0
; default(nuint)
a new nuint()
jsou ekvivalentní (nuint)0
.
typeof(nint)
je typeof(IntPtr)
; typeof(nuint)
je typeof(UIntPtr)
.
sizeof(nint)
a sizeof(nuint)
jsou podporované, ale vyžadují kompilaci v nebezpečném kontextu (podle potřeby pro sizeof(IntPtr)
a sizeof(UIntPtr)
).
Hodnoty nejsou konstanty kompilace.
sizeof(nint)
se implementuje jako sizeof(IntPtr)
místo IntPtr.Size
; sizeof(nuint)
se implementuje jako sizeof(UIntPtr)
místo UIntPtr.Size
.
Diagnostika kompilátoru pro odkazy na typy zahrnující nint
nebo nuint
hlásí nint
nebo nuint
místo IntPtr
nebo UIntPtr
.
Metaúdaje
nint
a nuint
jsou reprezentovány v metadatech jako System.IntPtr
a System.UIntPtr
.
Odkazy na typy, které obsahují nint
nebo nuint
, se emitují s System.Runtime.CompilerServices.NativeIntegerAttribute
, aby indikovali, které části odkazu na typ jsou nativní čísla integer.
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;
}
}
Kódování odkazů na typ s NativeIntegerAttribute
je popsáno v NativeIntegerAttribute.md.
Alternativy
Alternativou k výše uvedenému přístupu k vymazání typu je zavedení nových typů: System.NativeInt
a System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Různé typy by umožňovaly přetížení odlišné od IntPtr
a umožnily by specifické parsování a ToString()
.
CLR by mělo více práce, aby tyto typy zvládalo efektivně, což zmaří primární účel této funkce - efektivitu.
Spolupráce s existujícím nativním int kódem, který používá IntPtr
, by byla obtížnější.
Další alternativou je přidání nativní podpory int pro IntPtr
v rámci, ale bez podpory konkrétního kompilátoru.
Kompilátor automaticky podporuje všechny nové převody a aritmetické operace.
Jazyk ale neposkytuje klíčová slova, konstanty ani checked
operace.
Schůzky o designu
- 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