Delen via


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 nuintzijn. 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 en conv.i is een teken-uitbreidende conversie naar een systeemeigen geheel getal.
  • checked contexten voor zowel verruimende als beperkende zijn:
    • conv.ovf.* voor signed to *
    • conv.ovf.*.un voor unsigned to *
  • unchecked contexten voor breder maken zijn:
    • conv.i* voor signed to * (waarbij * de doelbreedte is)
    • conv.u* voor unsigned to * (waarbij * de doelbreedte is)
  • unchecked contexten voor het beperken van zijn:
    • conv.i* voor any to signed * (waarbij * de doelbreedte is)
    • conv.u* voor any to unsigned * (waarbij * de doelbreedte is)

Enkele voorbeelden:

  • sbyte to nint en sbyte to nuintconv.i gebruiken terwijl byte to nint en byte to nuintconv.u gebruiken, omdat ze allemaal breder worden.
  • nint to byte en nuint to byteconv.u1 gebruiken terwijl nint to sbyte en nuint to sbyteconv.i1gebruiken. Voor byte, sbyte, shorten ushort is het 'stacktype' int32. conv.i1 betekent effectief 'een signed byte maken en vervolgens voortzetten tot int32', terwijl conv.u1 betekent 'een unsigned byte maken en vervolgens nul-uitbreiden tot int32'.
  • checked void* to nint gebruikt conv.ovf.i.un op dezelfde manier als checked void* to longconv.ovf.i8.ungebruikt.
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 naar B;
  • een expliciete nulconversie als er een expliciete omzetting is van A naar B;
  • 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 naar Bis;
  • anders ongeldig.

Conversie van Nullable<A> naar Nullable<B> is:

  • een identiteitsconversie als er een identiteitsconversie is van A naar B;
  • een expliciete nullable conversie als er een impliciete of expliciete numerieke conversie van A naar Bis;
  • 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 nuintis.

(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 ulongis, wordt de andere operand geconverteerd naar het type ulongof treedt er een bindingstijdfout op als de andere operand van het type sbyte, short, int, nintof longis.
  • Anders, als een van de operanden van het type nuintis, wordt de andere operand geconverteerd naar het type nuint, of er treedt een bindingstijdfout op als de andere operand van het type sbyte, short, int, nintof longis.
  • Als een van beide operanden van het type longis, wordt de andere operand geconverteerd naar het type long.
  • Als een van beide operanden van het type uint is en de andere operand van het type sbyte, short, nint, of int, worden beide operanden geconverteerd naar het type long.
  • Als een van beide operanden van het type uintis, wordt de andere operand geconverteerd naar het type uint.
  • Als een operand van het type nintis, wordt de andere operand geconverteerd naar het type nint.
  • 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.UIntPtrworden 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.UIntPtrworden 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.UIntPtrworden 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 nintISerializable, 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