Gehele getallen met systeemeigen grootte
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting) .
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Kampioenprobleem: https://github.com/dotnet/csharplang/issues/435
Samenvatting
Taalondersteuning voor geheel getaltypes met inheemse grootte, zowel gesigneerd als niet-gesigneerd.
De motivatie is voor interop-scenario's en voor bibliotheken op laag niveau.
Ontwerpen
De id's nint
en nuint
zijn nieuwe contextuele trefwoorden die systeemeigen ondertekende en niet-ondertekende gehele getallen vertegenwoordigen.
De id's worden alleen behandeld als trefwoorden wanneer de naamzoekactie geen levensvatbaar resultaat op die programmalocatie vindt.
nint x = 3;
_ = nint.Equals(x, 3);
De typen nint
en nuint
worden vertegenwoordigd door de onderliggende typen System.IntPtr
en System.UIntPtr
, waarbij de compiler extra conversies en bewerkingen voor deze typen als systeemeigen gehele getallen mogelijk maakt.
Constanten
Constante expressies kunnen van het type nint
of nuint
zijn.
Er is geen directe syntaxis voor inheemse int-literals. Impliciete of expliciete casts van andere integrale constante waarden kunnen in plaats daarvan worden gebruikt: const nint i = (nint)42;
.
nint
constanten zijn in het bereik [ int.MinValue
, int.MaxValue
].
nuint
constanten zijn in het bereik [ uint.MinValue
, uint.MaxValue
].
Er zijn geen MinValue
- of MaxValue
velden in nint
of nuint
, omdat deze waarden, behalve nuint.MinValue
, niet als constanten kunnen worden verzonden.
Constant vouwen wordt ondersteund voor alle unaire operatoren { +
, -
, ~
} en binaire operatoren { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Constante vouwbewerkingen worden geëvalueerd met Int32
en UInt32
operanden in plaats van systeemeigen ints voor consistent gedrag, ongeacht het compilerplatform.
Als de bewerking resulteert in een constante waarde van 32-bits, wordt constante vouwing uitgevoerd tijdens de compilatie.
Anders wordt de bewerking tijdens runtime uitgevoerd en wordt deze niet beschouwd als een constante.
Conversies
Er is een identiteitsconversie tussen nint
en IntPtr
, en tussen nuint
en UIntPtr
.
Er is een identiteitsconversie tussen samengestelde typen die alleen verschillen door native ints en onderliggende typen: arrays, Nullable<>
, samengestelde typen en tuples.
De onderstaande tabellen hebben betrekking op de conversies tussen speciale typen.
(De IL voor elke conversie bevat de varianten voor unchecked
en checked
contexten als ze verschillend zijn.)
Algemene opmerkingen in de onderstaande tabel:
-
conv.u
is een nul-uitbreidende conversie naar een systeemeigen geheel getal enconv.i
is een teken-uitbreidende conversie naar een systeemeigen geheel getal. -
checked
contexten voor zowel verruimende als beperkende zijn:-
conv.ovf.*
voorsigned to *
-
conv.ovf.*.un
voorunsigned to *
-
-
unchecked
contexten voor breder maken zijn:-
conv.i*
voorsigned to *
(waarbij * de doelbreedte is) -
conv.u*
voorunsigned to *
(waarbij * de doelbreedte is)
-
-
unchecked
contexten voor het beperken van zijn:-
conv.i*
voorany to signed *
(waarbij * de doelbreedte is) -
conv.u*
voorany to unsigned *
(waarbij * de doelbreedte is)
-
Enkele voorbeelden:
-
sbyte to nint
ensbyte to nuint
conv.i
gebruiken terwijlbyte to nint
enbyte to nuint
conv.u
gebruiken, omdat ze allemaal breder worden. -
nint to byte
ennuint to byte
conv.u1
gebruiken terwijlnint to sbyte
ennuint to sbyte
conv.i1
gebruiken. Voorbyte
,sbyte
,short
enushort
is het 'stacktype'int32
.conv.i1
betekent effectief 'een signed byte maken en vervolgens voortzetten tot int32', terwijlconv.u1
betekent 'een unsigned byte maken en vervolgens nul-uitbreiden tot int32'. -
checked void* to nint
gebruiktconv.ovf.i.un
op dezelfde manier alschecked void* to long
conv.ovf.i8.un
gebruikt.
Operand | Doel | Conversie | IL |
---|---|---|---|
object |
nint |
Uitpakken | unbox |
void* |
nint |
PointerToVoid | nop / conv.ovf.i.un |
sbyte |
nint |
ImplicitNumeric | conv.i |
byte |
nint |
ImplicitNumeric | 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 |
Identiteit | |
UIntPtr |
nint |
Geen | |
object |
nuint |
Uitpakken | 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 |
ImplicitNumeric | 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 |
Geen | |
UIntPtr |
nuint |
Identiteit | |
Opsomming | nint |
ExplicieteEnumeratie | |
Opsomming | nuint |
ExplicieteEnumeratie |
Operand | Doel | Conversie | IL |
---|---|---|---|
nint |
object |
Boksen | box |
nint |
void* |
PointerToVoid | nop / conv.ovf.u |
nint |
nuint |
ExplicitNumeric |
conv.u (kan worden weggelaten) / conv.ovf.u |
nint |
sbyte |
ExplicitNumeric | conv.i1 / conv.ovf.i1 |
nint |
byte |
ExplicitNumeric | 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 |
Identiteit | |
nint |
UIntPtr |
Geen | |
nint |
Opsomming | ExplicieteEnumeratie | |
nuint |
object |
Boksen | box |
nuint |
void* |
PointerToVoid | Nop |
nuint |
nint |
ExplicitNumeric |
conv.i (kan worden weggelaten) / 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 |
Geen | |
nuint |
UIntPtr |
Identiteit | |
nuint |
Opsomming | ExplicieteEnumeratie |
Conversie van A
naar Nullable<B>
is:
- een impliciete nulleerbare conversie als er sprake is van een identiteitsconversie of een impliciete conversie van
A
naarB
; - een expliciete nulconversie als er een expliciete omzetting is van
A
naarB
; - anders ongeldig.
Conversie van Nullable<A>
naar B
is:
- een expliciete nullbare conversie als er een identiteitsconversie of impliciete of expliciete numerieke conversie van
A
naarB
is; - anders ongeldig.
Conversie van Nullable<A>
naar Nullable<B>
is:
- een identiteitsconversie als er een identiteitsconversie is van
A
naarB
; - een expliciete nullable conversie als er een impliciete of expliciete numerieke conversie van
A
naarB
is; - anders ongeldig.
Bedieners
De vooraf gedefinieerde operators zijn als volgt.
Deze operators worden tijdens overbelastingsresolutie beschouwd op basis van normale regels voor impliciete conversies als ten minste één van de operanden van het type nint
of nuint
is.
(De IL bevat de varianten voor elk operator voor de unchecked
- en checked
-contexten indien verschillend.)
Unaire | Operatorhandtekening | 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 |
Binair | Operatorhandtekening | 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 |
Voor sommige binaire operators ondersteunen de IL-operators aanvullende operandtypen (zie ECMA-335 III.1.5 Operand type tabel). Maar de set operandtypen die door C# worden ondersteund, is beperkt voor eenvoud en voor consistentie met bestaande operators in de taal.
Opgeheven versies van de operators, waarbij de argumenten en retourtypen worden nint?
en nuint?
, worden ondersteund.
Samengestelde toewijzingsbewerkingen x op= y
waarbij x
of y
systeemeigen ints zijn, voldoen aan dezelfde regels als bij andere primitieve typen met vooraf gedefinieerde operators.
De expressie is specifiek gebonden als x = (T)(x op y)
waarbij T
het type x
is en waarbij x
slechts eenmaal wordt geëvalueerd.
De shiftoperators moeten het aantal bits maskeren dat moet worden verplaatst- naar 5 bits als sizeof(nint)
4 is en tot 6 bits als sizeof(nint)
8 is.
(zie §12.11) in de C#-specificatie).
De C#9-compiler rapporteert fouten die zijn gekoppeld aan vooraf gedefinieerde systeemeigen gehele getallen bij het compileren met een eerdere taalversie, maar maakt het gebruik van vooraf gedefinieerde conversies naar en van systeemeigen gehele getallen mogelijk.
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
}
}
Rekenkundige bewerkingen met pointers
Er zijn geen vooraf gedefinieerde operators in C# voor het optellen of aftrekken van aanwijzers met natuurlijke gehele getallen.
In plaats daarvan worden nint
- en nuint
-waarden gepromoveerd tot long
en ulong
, en gebruiken aanwijzerberekeningen vooraf gedefinieerde operatoren voor die typen.
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)
Binaire numerieke promoties
De binaire numerieke promoties, zoals beschreven in de informatieve tekst (zie §12.4.7.3in de C#-specificatie), worden als volgt bijgewerkt:
- …
- Als een operand van het type
ulong
is, wordt de andere operand geconverteerd naar het typeulong
of treedt er een bindingstijdfout op als de andere operand van het typesbyte
,short
,int
,nint
oflong
is.- Anders, als een van de operanden van het type
nuint
is, wordt de andere operand geconverteerd naar het typenuint
, of er treedt een bindingstijdfout op als de andere operand van het typesbyte
,short
,int
,nint
oflong
is.- Als een van beide operanden van het type
long
is, wordt de andere operand geconverteerd naar het typelong
.- Als een van beide operanden van het type
uint
is en de andere operand van het typesbyte
,short
,nint
, ofint
, worden beide operanden geconverteerd naar het typelong
.- Als een van beide operanden van het type
uint
is, wordt de andere operand geconverteerd naar het typeuint
.- Als een operand van het type
nint
is, wordt de andere operand geconverteerd naar het typenint
.- Anders worden beide operanden geconverteerd naar het type
int
.
Dynamisch
De conversies en operators worden gesynthetiseerd door de compiler en maken geen deel uit van de onderliggende IntPtr
en UIntPtr
typen.
Als gevolg hiervan zijn deze conversies en operators niet beschikbaar van de runtime-binder voor 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'
Typeleden
De enige constructor voor nint
of nuint
is de constructor met parameterloze waarden.
De volgende leden van System.IntPtr
en System.UIntPtr
worden expliciet uitgesloten van nint
of 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();
De resterende leden van System.IntPtr
en System.UIntPtr
worden impliciet opgenomen in nint
en nuint
. Voor .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Interfaces die worden geïmplementeerd door System.IntPtr
en System.UIntPtr
worden impliciet opgenomen in nint
en nuint
, waarbij exemplaren van de onderliggende typen worden vervangen door de bijbehorende systeemeigen gehele getallen.
Als IntPtr
bijvoorbeeld ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
implementeert, implementeert nint
ISerializable, IEquatable<nint>, IComparable<nint>
.
Overschrijven, verbergen en implementeren
nint
en System.IntPtr
, en nuint
en System.UIntPtr
, worden beschouwd als gelijkwaardig voor het overschrijven, verbergen en implementeren.
Overbelastingen kunnen niet verschillen per nint
en System.IntPtr
, en alleen nuint
en System.UIntPtr
.
Overschrijvingen en implementaties kunnen alleen verschillen per nint
en System.IntPtr
, of nuint
en System.UIntPtr
.
Methoden verbergen andere methoden die verschillen per nint
en System.IntPtr
, of alleen nuint
en System.UIntPtr
.
Allerlei
nint
en nuint
expressies die worden gebruikt als matrixindexen, worden zonder conversie verzonden.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
en nuint
kunnen niet worden gebruikt als een enum
basistype van C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Lees- en schrijfbewerkingen zijn atomisch voor nint
en nuint
.
Velden kunnen worden gemarkeerd als volatile
voor typen nint
en nuint
.
ECMA-334 15.5.4 bevat echter geen enum
met basistype System.IntPtr
of System.UIntPtr
.
default(nint)
en new nint()
gelijk zijn aan (nint)0
; default(nuint)
en new nuint()
zijn gelijk aan (nuint)0
.
typeof(nint)
is typeof(IntPtr)
; typeof(nuint)
is typeof(UIntPtr)
.
sizeof(nint)
en sizeof(nuint)
worden ondersteund, maar vereisen compilatie in een onveilige context (zoals vereist voor sizeof(IntPtr)
en sizeof(UIntPtr)
).
De waarden zijn geen compileertijdconstanten.
sizeof(nint)
wordt geïmplementeerd als sizeof(IntPtr)
in plaats van IntPtr.Size
; sizeof(nuint)
wordt geïmplementeerd als sizeof(UIntPtr)
in plaats van UIntPtr.Size
.
Compilerdiagnostiek voor typeverwijzingen met betrekking tot nint
of nuint
rapporteert nint
of nuint
in plaats van IntPtr
of UIntPtr
.
Metagegevens
nint
en nuint
worden weergegeven in metagegevens als System.IntPtr
en System.UIntPtr
.
Typeverwijzingen die nint
of nuint
bevatten, worden verzonden met een System.Runtime.CompilerServices.NativeIntegerAttribute
om aan te geven welke delen van de typeverwijzing native ints zijn.
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;
}
}
De codering van typeverwijzingen met NativeIntegerAttribute
wordt behandeld in NativeIntegerAttribute.md.
Alternatieven
Een alternatief voor de bovenstaande methode voor 'typeverwijdering' is het introduceren van nieuwe typen: System.NativeInt
en System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
Verschillende typen zouden overbelastingen mogelijk maken die verschillen van IntPtr
en zouden afzonderlijke parsering en ToString()
toestaan.
Maar er zou meer werk zijn voor de CLR om deze typen efficiënt te verwerken, waardoor het primaire doel van de functie - efficiëntie wordt verslagen.
En interop met bestaande systeemeigen int-code die gebruikmaakt van IntPtr
zou moeilijker zijn.
Een ander alternatief is om meer systeemeigen int-ondersteuning toe te voegen voor IntPtr
in het framework, maar zonder specifieke compilerondersteuning.
Nieuwe conversies en rekenkundige bewerkingen worden automatisch ondersteund door de compiler.
Maar de taal biedt geen trefwoorden, constanten of checked
bewerkingen.
Ontwerpvergaderingen
- 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