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 IntPtr
och 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 ochconv.i
är en teckenutvidgande konvertering till inbyggt heltal. -
checked
kontexter för både utvidgning och inskränkning är:-
conv.ovf.*
försigned to *
-
conv.ovf.*.un
förunsigned to *
-
-
unchecked
kontexter för breddning är:-
conv.i*
försigned to *
(där * är målbredden) -
conv.u*
förunsigned to *
(där * är målbredden)
-
-
unchecked
kontexter för avsmalning är:-
conv.i*
förany to signed *
(där * är målbredden) -
conv.u*
förany to unsigned *
(där * är målbredden)
-
Ta några exempel:
-
sbyte to nint
ochsbyte to nuint
använderconv.i
medanbyte to nint
ochbyte to nuint
använderconv.u
eftersom de alla breddar. -
nint to byte
ochnuint to byte
använderconv.u1
medannint to sbyte
ochnuint to sbyte
använderconv.i1
. Förbyte
,sbyte
,short
ochushort
är "stacktypen"int32
. Såconv.i1
är i själva verket "nedkonverterad till en signerad byte och sedan teckenförlängd upp till int32" medanconv.u1
i praktiken "nedkonverterad till en osignerad byte och sedan nollförlängd upp till int32". -
checked void* to nint
använderconv.ovf.i.un
på samma sätt somchecked void* to long
använderconv.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
tillB
; - en explicit nullbar konvertering om det finns en explicit konvertering från
A
tillB
; - 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
tillB
; - annars ogiltig.
Konvertering från Nullable<A>
till Nullable<B>
är:
- en identitetskonvertering om det finns en identitetskonvertering från
A
tillB
; - en explicit nullbar konvertering om det finns en implicit eller explicit numerisk konvertering från
A
tillB
; - 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 typenulong
, eller så inträffar ett bindningstidsfel om den andra operanden är av typensbyte
,short
,int
,nint
ellerlong
.- Annars, om operand är av typen
nuint
, konverteras den andra operanden till typennuint
, eller om ett bindningstidsfel inträffar om den andra operanden är av typensbyte
,short
,int
,nint
ellerlong
.- Om någon av operanderna är av typen
long
konverteras annars den andra operanden till typenlong
.- Om någon av operanderna är av typen
uint
och den andra operanden är av typensbyte
,short
,nint
, ellerint
konverteras båda operanderna till typenlong
.- Om någon av operanderna är av typen
uint
konverteras annars den andra operanden till typenuint
.- Om någon av operanderna är av typen
nint
konverteras annars den andra operanden till typennint
.- 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.UIntPtr
undantas 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.UIntPtr
ingå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.UIntPtr
ingå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 nint
ISerializable, 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
- 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