Dela via


Heltal i naturlig storlek

Not

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader finns i de relevanta anteckningarna från språkdesignmötena (LDM).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-fråga: https://github.com/dotnet/csharplang/issues/435

Sammanfattning

Språkstöd för inbyggda signerade och osignerade heltalstyper.

Motiveringen är för interop-scenarier och för bibliotek på låg nivå.

Utformning

Identifierarna nint och nuint är nya kontextuella nyckelord som representerar inbyggda signerade och osignerade heltalstyper. Identifierarna behandlas endast som nyckelord när namnsökningen inte hittar ett genomförbart resultat på den programplatsen.

nint x = 3;
_ = nint.Equals(x, 3);

Typerna nint och nuint representeras av de underliggande typerna System.IntPtr och System.UIntPtr, där kompilatorn tillhandahåller ytterligare konverteringar och operationer för dessa typer som inbyggda heltal.

Konstanter

Konstanta uttryck kan vara av typen nint eller nuint. Det finns ingen direkt syntax för interna int-literaler. Implicita eller explicita avgjutningar av andra helta konstanta värden kan användas i stället: const nint i = (nint)42;.

nint konstanter finns i intervallet [ int.MinValue, int.MaxValue ].

nuint konstanter finns i intervallet [ uint.MinValue, uint.MaxValue ].

Det finns inga MinValue- eller MaxValue fält på nint eller nuint eftersom dessa värden, förutom nuint.MinValue, inte kan genereras som konstanter.

Konstant vikning stöds för alla unary operatorer { +, -, ~ } och binära operatorer { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }. Ständiga vikningsåtgärder utvärderas med Int32 och UInt32 operander i stället för inbyggda ints för konsekvent beteende oavsett kompilatorplattform. Om åtgärden resulterar i ett konstant värde i 32 bitar utförs konstant vikning vid kompileringstid. Annars utförs operationen vid körning och betraktas inte som en konstant.

Omvandlingar

Det finns en identitetskonvertering mellan nint och IntPtroch mellan nuint och UIntPtr. Det finns en identitetskonvertering mellan sammansatta typer som enbart skiljer sig åt i fråga om de inbyggda heltalen och de underliggande typerna: arrayer, Nullable<>, konstruerade typer och tupler.

Tabellerna nedan beskriver konverteringarna mellan specialtyper. (IL för varje konvertering innehåller varianterna för unchecked och checked kontexter om de är olika.)

Allmänna anteckningar i tabellen nedan:

  • conv.u är en nollutvidgande konvertering till inbyggt heltal och conv.i är en teckenutvidgande konvertering till inbyggt heltal.
  • checked kontexter för både utvidgning och inskränkning är:
    • conv.ovf.* för signed to *
    • conv.ovf.*.un för unsigned to *
  • unchecked kontexter för breddning är:
    • conv.i* för signed to * (där * är målbredden)
    • conv.u* för unsigned to * (där * är målbredden)
  • unchecked kontexter för avsmalning är:
    • conv.i* för any to signed * (där * är målbredden)
    • conv.u* för any to unsigned * (där * är målbredden)

Ta några exempel:

  • sbyte to nint och sbyte to nuint använder conv.i medan byte to nint och byte to nuint använder conv.u eftersom de alla breddar.
  • nint to byte och nuint to byte använder conv.u1 medan nint to sbyte och nuint to sbyte använder conv.i1. För byte, sbyte, shortoch ushort är "stacktypen" int32. Så conv.i1 är i själva verket "nedkonverterad till en signerad byte och sedan teckenförlängd upp till int32" medan conv.u1 i praktiken "nedkonverterad till en osignerad byte och sedan nollförlängd upp till int32".
  • checked void* to nint använder conv.ovf.i.un på samma sätt som checked void* to long använder conv.ovf.i8.un.
Operand Mål Omvandling IL
object nint Öppna förpackningen unbox
void* nint PointerToVoid nop/conv.ovf.i.un
sbyte nint Implicit Numerisk conv.i
byte nint Implicit Numerisk conv.u
short nint Implicit Numerisk conv.i
ushort nint Implicit Numerisk conv.u
int nint Implicit Numerisk conv.i
uint nint ExplicitNumeriskt conv.u / conv.ovf.i.un
long nint ExplicitNumeriskt conv.i / conv.ovf.i
ulong nint ExplicitNumeriskt conv.i / conv.ovf.i.un
char nint Implicit Numerisk conv.u
float nint ExplicitNumeriskt conv.i / conv.ovf.i
double nint ExplicitNumeriskt conv.i / conv.ovf.i
decimal nint ExplicitNumeriskt long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i
IntPtr nint Identitet
UIntPtr nint Ingen
object nuint Öppna förpackningen unbox
void* nuint PointerToVoid Nop
sbyte nuint ExplicitNumeriskt conv.i / conv.ovf.u
byte nuint Implicit Numerisk conv.u
short nuint ExplicitNumeriskt conv.i / conv.ovf.u
ushort nuint Implicit Numerisk conv.u
int nuint ExplicitNumeriskt conv.i / conv.ovf.u
uint nuint Implicit Numerisk conv.u
long nuint ExplicitNumeriskt conv.u / conv.ovf.u
ulong nuint ExplicitNumeriskt conv.u / conv.ovf.u.un
char nuint Implicit Numerisk conv.u
float nuint ExplicitNumeriskt conv.u / conv.ovf.u
double nuint ExplicitNumeriskt conv.u / conv.ovf.u
decimal nuint ExplicitNumeriskt ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un
IntPtr nuint Ingen
UIntPtr nuint Identitet
Uppräkning nint ExplicitEnumeration
Uppräkning nuint ExplicitEnumeration
Operand Mål Omvandling IL
nint object Boxning box
nint void* PointerToVoid nop/conv.ovf.u
nint nuint ExplicitNumeriskt conv.u (kan utelämnas) /conv.ovf.u
nint sbyte ExplicitNumeriskt conv.i1 / conv.ovf.i1
nint byte ExplicitNumeriskt conv.u1 / conv.ovf.u1
nint short ExplicitNumeriskt conv.i2 / conv.ovf.i2
nint ushort ExplicitNumeriskt conv.u2 / conv.ovf.u2
nint int ExplicitNumeriskt conv.i4 / conv.ovf.i4
nint uint ExplicitNumeriskt conv.u4 / conv.ovf.u4
nint long Implicit Numerisk conv.i8
nint ulong ExplicitNumeriskt conv.i8 / conv.ovf.u8
nint char ExplicitNumeriskt conv.u2 / conv.ovf.u2
nint float Implicit Numerisk conv.r4
nint double Implicit Numerisk conv.r8
nint decimal Implicit Numerisk conv.i8 decimal decimal.op_Implicit(long)
nint IntPtr Identitet
nint UIntPtr Ingen
nint Uppräkning ExplicitEnumeration
nuint object Boxning box
nuint void* PointerToVoid Nop
nuint nint ExplicitNumeriskt conv.i(kan utelämnas) /conv.ovf.i.un
nuint sbyte ExplicitNumeriskt conv.i1 / conv.ovf.i1.un
nuint byte ExplicitNumeriskt conv.u1 / conv.ovf.u1.un
nuint short ExplicitNumeriskt conv.i2 / conv.ovf.i2.un
nuint ushort ExplicitNumeriskt conv.u2 / conv.ovf.u2.un
nuint int ExplicitNumeriskt conv.i4 / conv.ovf.i4.un
nuint uint ExplicitNumeriskt conv.u4 / conv.ovf.u4.un
nuint long ExplicitNumeriskt conv.u8 / conv.ovf.i8.un
nuint ulong Implicit Numerisk conv.u8
nuint char ExplicitNumeriskt conv.u2 / conv.ovf.u2.un
nuint float Implicit Numerisk conv.r.un conv.r4
nuint double Implicit Numerisk conv.r.un conv.r8
nuint decimal Implicit Numerisk conv.u8 decimal decimal.op_Implicit(ulong)
nuint IntPtr Ingen
nuint UIntPtr Identitet
nuint Uppräkning ExplicitEnumeration

Konvertering från A till Nullable<B> är:

  • en implicit nullbar konvertering om det finns en identitetskonvertering eller implicit konvertering från A till B;
  • en explicit nullbar konvertering om det finns en explicit konvertering från A till B;
  • annars ogiltig.

Konvertering från Nullable<A> till B är:

  • en explicit nullbar konvertering om det finns en identitetskonvertering eller implicit eller explicit numerisk konvertering från A till B;
  • annars ogiltig.

Konvertering från Nullable<A> till Nullable<B> är:

  • en identitetskonvertering om det finns en identitetskonvertering från A till B;
  • en explicit nullbar konvertering om det finns en implicit eller explicit numerisk konvertering från A till B;
  • annars ogiltig.

Operatörer

De fördefinierade operatorerna är följande. Dessa operatorer beaktas vid överbelastningsmatchning baserat på normala regler för implicita konverteringar om minst en av operanderna är av typen nint eller nuint.

(IL för varje operator innehåller varianterna för unchecked och checked kontexter om de är olika.)

Unär Operator-signatur 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är Operator-signatur 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

För vissa binära operatorer stöder IL-operatorerna ytterligare operandtyper (se ECMA-335 III.1.5 Operand-typtabell). Men uppsättningen operandtyper som stöds av C# är begränsad för enkelhetens skull och för konsekvens med befintliga operatorer på språket.

Lyftade versioner av operatorerna, där argumenten och returtyperna är nint? och nuint?, stöds.

Sammansatta tilldelningar x op= y där x eller y är naturliga heltal följer samma regler som med andra primitiva typer och använder fördefinierade operatorer. Mer specifikt är uttrycket bundet som x = (T)(x op y) där T är typen av x och där x endast utvärderas en gång.

Skiftoperatorerna bör maskera antalet bitar som ska flyttas – till 5 bitar om sizeof(nint) är 4 och till 6 bitar om sizeof(nint) är 8. (se §12.11) i C#-specifikationen).

C#9-kompilatorn rapporterar felbindning till fördefinierade interna heltalsoperatorer vid kompilering med en tidigare språkversion, men tillåter användning av fördefinierade konverteringar till och från inbyggda heltal.

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
    }
}

Pekarearitmetik

Det finns inga fördefinierade operatorer i C# för pekartillägg eller subtraktion med inbyggda heltalsförskjutningar. I stället höjs nint och nuint värden till long och ulong och pekarens aritmetik använder fördefinierade operatorer för dessa typer.

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ära numeriska promotioner

De binära numeriska kampanjerna informativ text (se §12.4.7.3) i C#-specifikationen) uppdateras på följande sätt:

  • Annars, om någon av operanderna är av typen ulong, konverteras den andra operanden till typen ulong, eller så inträffar ett bindningstidsfel om den andra operanden är av typen sbyte, short, int, ninteller long.
  • Annars, om operand är av typen nuint, konverteras den andra operanden till typen nuint, eller om ett bindningstidsfel inträffar om den andra operanden är av typen sbyte, short, int, ninteller long.
  • Om någon av operanderna är av typen longkonverteras annars den andra operanden till typen long.
  • Om någon av operanderna är av typen uint och den andra operanden är av typen sbyte, short, nint, eller intkonverteras båda operanderna till typen long.
  • Om någon av operanderna är av typen uintkonverteras annars den andra operanden till typen uint.
  • Om någon av operanderna är av typen nintkonverteras annars den andra operanden till typen nint.
  • Annars konverteras båda operanderna till typen int.

Dynamisk

Konverteringarna och operatorerna syntetiseras av kompilatorn och ingår inte i de underliggande IntPtr och UIntPtr typerna. Därför är dessa konverteringar och operatorer inte tillgängliga från körningsbindningen för 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'

Typmedlemmar

Den enda konstruktorn för nint eller nuint är konstruktorn utan parameter.

Följande medlemmar i System.IntPtr och System.UIntPtrundantas uttryckligen från nint eller 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();

Återstående medlemmar i System.IntPtr och System.UIntPtringår implicit i nint och nuint. För .NET Framework 4.7.2:

public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);

Gränssnitt som implementeras av System.IntPtr och System.UIntPtringår implicit i nint och nuint, med förekomster av de underliggande typerna ersatta av motsvarande interna heltalstyper. Om IntPtr till exempel implementerar ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>implementerar nintISerializable, IEquatable<nint>, IComparable<nint>.

Åsidosätta, dölja och implementera

nint och System.IntPtr, och nuint och System.UIntPtr, anses vara likvärdiga för att åsidosätta, dölja och implementera.

Överlagringar kan inte skilja sig åt beroende på nint och System.IntPtr, och nuint och System.UIntPtr, ensamma. Åsidosättningar och implementeringar kan skilja sig åt beroende på enbart nint och System.IntPtr, eller enbart nuint och System.UIntPtr. Metoder döljer andra metoder som skiljer sig mellan nint och System.IntPtr, eller nuint och System.UIntPtr, ensamma.

Diverse

nint och nuint uttryck som används som matrisindex genereras utan konvertering.

static object GetItem(object[] array, nint index)
{
    return array[index]; // ok
}

nint och nuint kan inte användas som en enum bastyp från C#.

enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}

Läsningar och skrivningar är atomära för nint och nuint.

Fält kan markeras volatile för typer nint och nuint. ECMA-334 15.5.4 omfattar dock inte enum med bastyp System.IntPtr eller System.UIntPtr.

default(nint) och new nint() motsvarar (nint)0. default(nuint) och new nuint() motsvarar (nuint)0.

typeof(nint) är typeof(IntPtr); typeof(nuint) är typeof(UIntPtr).

sizeof(nint) och sizeof(nuint) stöds men kräver kompilering i en osäker kontext (som krävs för sizeof(IntPtr) och sizeof(UIntPtr)). Värdena är inte kompileringstidskonstanter. sizeof(nint) implementeras som sizeof(IntPtr) i stället för IntPtr.Size. sizeof(nuint) implementeras som sizeof(UIntPtr) i stället för UIntPtr.Size.

Kompilatordiagnostik för typreferenser som inbegriper nint eller nuint rapporterar nint eller nuint i stället för IntPtr eller UIntPtr.

Metadata

nint och nuint representeras i metadata som System.IntPtr och System.UIntPtr.

Typreferenser som innehåller nint eller nuint genereras med en System.Runtime.CompilerServices.NativeIntegerAttribute som anger vilka delar av typreferensen som är inbyggda ints.

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;
    }
}

Kodningen av typreferenser med NativeIntegerAttribute beskrivs i NativeIntegerAttribute.md.

Alternativ

Ett alternativ till metoden "typ radering" ovan är att introducera nya typer: System.NativeInt och System.NativeUInt.

public readonly struct NativeInt
{
    public IntPtr Value;
}

Distinkta typer skulle tillåta överlagring som skiljer sig från IntPtr och skulle tillåta distinkt parsning och ToString(). Men det skulle innebära mer arbete för CLR att effektivt hantera dessa typer, vilket motverkar huvudsyftet med funktionen - effektivitet. Och interoperation med befintlig int-intern kod som använder IntPtr skulle vara svårare.

Ett annat alternativ är att lägga till mer internt int-stöd för IntPtr i ramverket men utan något specifikt kompilatorstöd. Alla nya konverteringar och aritmetiska åtgärder stöds automatiskt av kompilatorn. Men språket skulle inte ge nyckelord, konstanter eller checked åtgärder.

Designa möten