Numeri interi di dimensioni native
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle note pertinenti del language design meeting (LDM) .
Per ulteriori dettagli sul processo di adozione degli speclet di funzionalità nello standard del linguaggio C#, consultare l'articolo sulle specifiche di .
Problema del campione: https://github.com/dotnet/csharplang/issues/435
Sommario
Supporto linguistico per tipi di interi con segno e senza segno di dimensioni native.
La motivazione è per scenari di interoperabilità e per librerie a basso livello.
Disegno
Gli identificatori nint
e nuint
sono nuovi termini contestuali che rappresentano tipi interi con segno e senza segno nativi.
Gli identificatori vengono trattati solo come parole chiave quando il controllo dei nomi non trova un risultato valido in quel punto del programma.
nint x = 3;
_ = nint.Equals(x, 3);
I tipi nint
e nuint
sono rappresentati dai tipi sottostanti System.IntPtr
e System.UIntPtr
con il compilatore che rende visibili conversioni e operazioni aggiuntive per tali tipi come numeri interi nativi.
Costanti
Le espressioni costanti possono essere di tipo nint
o nuint
.
Non esiste una sintassi diretta per i letterali interi nativi. È possibile usare invece cast impliciti o espliciti di altri valori costanti integrali: const nint i = (nint)42;
.
nint
costanti sono incluse nell'intervallo [ int.MinValue
, int.MaxValue
].
nuint
costanti sono incluse nell'intervallo [ uint.MinValue
, uint.MaxValue
].
Non sono presenti campi MinValue
o MaxValue
in nint
o nuint
perché, oltre a nuint.MinValue
, tali valori non possono essere emessi come costanti.
La riduzione costante è supportata per tutti gli operatori unari { +
, -
, ~
} e gli operatori binari { +
, -
, *
, /
, %
, ==
, !=
, <
, <=
, >
, >=
, &
, |
, ^
, <<
, >>
}.
Le operazioni di riduzione costante vengono valutate con Int32
e UInt32
operandi anziché int nativi per un comportamento coerente indipendentemente dalla piattaforma del compilatore.
Se l'operazione restituisce un valore costante in 32 bit, la riduzione costante viene eseguita in fase di compilazione.
In caso contrario, l'operazione viene eseguita in fase di esecuzione e non considerata una costante.
Conversioni
Esiste una conversione di identità tra nint
e IntPtr
e tra nuint
e UIntPtr
.
Esiste una conversione di identità tra tipi composti che differiscono solo per i tipi nativi e per i tipi sottostanti: matrici, Nullable<>
, tipi costruiti e tuple.
Le tabelle seguenti illustrano le conversioni tra tipi speciali.
L'IL per ogni conversione include le varianti per i contesti unchecked
e checked
se diversi.
Note generali sulla tabella seguente:
-
conv.u
è una conversione a estensione zero in intero nativo econv.i
è una conversione a estensione di segno in intero nativo. -
checked
contesti sia per allargamento che per restrizione:-
conv.ovf.*
persigned to *
-
conv.ovf.*.un
perunsigned to *
-
-
unchecked
contesti per l'ampliamento di sono:-
conv.i*
persigned to *
(dove * è la larghezza di destinazione) -
conv.u*
perunsigned to *
(dove * è la larghezza di destinazione)
-
-
unchecked
contesti di restringimento per sono:-
conv.i*
perany to signed *
(dove * è la larghezza di destinazione) -
conv.u*
perany to unsigned *
(dove * è la larghezza di destinazione)
-
Prendendo alcuni esempi:
-
sbyte to nint
esbyte to nuint
usanoconv.i
, mentrebyte to nint
ebyte to nuint
usanoconv.u
perché sono tutti che ampliano. -
nint to byte
enuint to byte
usareconv.u1
mentrenint to sbyte
enuint to sbyte
usareconv.i1
. Perbyte
,sbyte
,short
eushort
il "tipo di stack" èint32
. Quindiconv.i1
è effettivamente "ridotto a un byte con segno e quindi esteso nel segno fino a int32" mentreconv.u1
è effettivamente "ridotto a un byte senza segno e quindi esteso con zeri fino a int32". -
checked void* to nint
usaconv.ovf.i.un
allo stesso modo in cuichecked void* to long
usaconv.ovf.i8.un
.
Operando | Bersaglio | Conversione | IL |
---|---|---|---|
object |
nint |
Apertura della confezione | 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 |
Identità | |
UIntPtr |
nint |
Nessuno | |
object |
nuint |
Apertura della confezione | 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 |
Nessuno | |
UIntPtr |
nuint |
Identità | |
Enumerazione | nint |
Enumerazione Esplicita | |
Enumerazione | nuint |
Enumerazione Esplicita |
Operando | Bersaglio | Conversione | IL |
---|---|---|---|
nint |
object |
Boxe | box |
nint |
void* |
PointerToVoid | nop/conv.ovf.u |
nint |
nuint |
ExplicitNumeric |
conv.u (può essere omesso) /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 |
Identità | |
nint |
UIntPtr |
Nessuno | |
nint |
Enumerazione | Enumerazione Esplicita | |
nuint |
object |
Boxe | box |
nuint |
void* |
PointerToVoid | Nop |
nuint |
nint |
ExplicitNumeric |
conv.i (può essere omesso) /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 |
Nessuno | |
nuint |
UIntPtr |
Identità | |
nuint |
Enumerazione | Enumerazione Esplicita |
La conversione da A
a Nullable<B>
è:
- conversione implicita nullable se è presente una conversione di identità o una conversione implicita da
A
aB
; - una conversione esplicita "nullable" se è presente una conversione esplicita da
A
aB
; - in caso contrario, non è valido.
La conversione da Nullable<A>
a B
è:
- una conversione esplicita annullabile se esiste una conversione di identità o una conversione numerica, sia essa implicita o esplicita, da
A
aB
; - in caso contrario, non è valido.
La conversione da Nullable<A>
a Nullable<B>
è:
- una conversione di identità se è presente una conversione di identità da
A
aB
; - una conversione esplicita annullabile se è presente una conversione numerica implicita o esplicita da
A
aB
; - in caso contrario, non è valido.
Operatori
Gli operatori predefiniti sono i seguenti.
Questi operatori vengono considerati durante la risoluzione dell'overload in base alle normali regole per le conversioni implicite se almeno uno degli operandi è di tipo nint
o nuint
.
(Il IL per ogni operatore include le varianti per unchecked
e checked
se diversi i contesti.)
Unario | Firma dell'operatore | 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 |
Binario | Firma dell'operatore | 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 |
Per alcuni operatori binari, gli operatori IL supportano tipi di operandi aggiuntivi (vedere ECMA-335 III.1.5 tabella dei tipi di operandi). Ma il set di tipi di operando supportati da C# è limitato per semplicità e per coerenza con gli operatori esistenti nel linguaggio.
Sono supportate le versioni lifted degli operatori, in cui gli argomenti e i tipi restituiti sono nint?
e nuint?
.
Le operazioni di assegnazione composte x op= y
in cui x
o y
sono int nativi seguono le stesse regole di altri tipi primitivi con operatori predefiniti.
In particolare, l'espressione viene associata come x = (T)(x op y)
dove T
è il tipo di x
e dove x
viene valutata una sola volta.
Gli operatori di spostamento devono mascherare il numero di bit da spostare, riducendolo a 5 bit se sizeof(nint)
è 4 e a 6 bit se sizeof(nint)
è 8.
(vedere §12.11) nella specifica C#).
Il compilatore C#9 segnala errori di associazione a operatori integer nativi predefiniti durante la compilazione con una versione precedente del linguaggio, ma consentirà l'uso di conversioni predefinite da e verso numeri interi nativi.
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
}
}
Aritmetica del puntatore
Non esistono operatori predefiniti in C# per l'addizione o la sottrazione del puntatore con offset integer nativi.
Al contrario, i valori nint
e nuint
vengono promossi a long
e ulong
e l'aritmetica dei puntatori utilizza gli operatori predefiniti per tali tipi.
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)
Promozioni numeriche binarie
Le promozioni numeriche binarie nel testo informativo della specifica C# (vedere §12.4.7.3) sono state aggiornate come segue:
- …
- In caso contrario, se uno degli operandi è di tipo
ulong
, l'altro operando viene convertito nel tipoulong
o si verifica un errore di binding se l'altro operando è di tiposbyte
,short
,int
,nint
olong
.- In caso contrario, se uno degli operandi è di tipo
nuint
, l'altro operando viene convertito in tiponuint
o si verifica un errore di binding se l'altro operando è di tiposbyte
,short
,int
,nint
olong
.- In caso contrario, se uno degli operandi è di tipo
long
, l'altro operando viene convertito nel tipolong
.- In caso contrario, se uno degli operandi è di tipo
uint
e l'altro operando è di tiposbyte
,short
,nint
, oint
, entrambi gli operandi vengono convertiti nel tipolong
.- In caso contrario, se uno degli operandi è di tipo
uint
, l'altro operando viene convertito nel tipouint
.- In caso contrario, se uno degli operandi è di tipo
nint
, l'altro operando viene convertito nel tiponint
.- In caso contrario, entrambi gli operandi vengono convertiti in tipo
int
.
Dinamico
Le conversioni e gli operatori vengono sintetizzati dal compilatore e non fanno parte dei tipi IntPtr
e UIntPtr
sottostanti.
Di conseguenza, tali conversioni e operatori non sono disponibili dal binder di runtime per 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'
Membri del tipo
L'unico costruttore per nint
o nuint
è il costruttore senza parametri.
I membri seguenti di System.IntPtr
e System.UIntPtr
vengono esclusi in modo esplicito da nint
o 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();
I membri rimanenti di System.IntPtr
e System.UIntPtr
vengono inclusi in modo implicito in nint
e nuint
. Per .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
Le interfacce implementate da System.IntPtr
e System.UIntPtr
sono incluse implicitamente in in nint
e nuint
, con le occorrenze dei tipi sottostanti sostituite dai corrispondenti tipi interi nativi.
Ad esempio, se IntPtr
implementa ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>
, nint
implementa ISerializable, IEquatable<nint>, IComparable<nint>
.
Sovrascrivere, nascondere e implementare
nint
e System.IntPtr
, e nuint
e System.UIntPtr
, sono considerati equivalenti per l'override, l'occultamento e l'implementazione.
Gli overload non possono differire solo per nint
e System.IntPtr
, o nuint
e System.UIntPtr
.
Le sostituzioni e le implementazioni possono differire per nint
e System.IntPtr
, o nuint
e System.UIntPtr
, singolarmente.
I metodi nascondono altri metodi che differiscono solo per nint
e System.IntPtr
, o nuint
e System.UIntPtr
.
Misto
Le espressioni nint
e nuint
utilizzate come indici di array sono emesse senza conversione.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint
e nuint
non possono essere usati come tipo di base enum
da C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Le operazioni di lettura e scrittura sono atomiche per nint
e nuint
.
I campi possono essere contrassegnati volatile
per i tipi nint
e nuint
.
ECMA-334 15.5.4 non include enum
con base di tipo System.IntPtr
o System.UIntPtr
tuttavia.
default(nint)
e new nint()
sono equivalenti a (nint)0
; default(nuint)
e new nuint()
sono equivalenti a (nuint)0
.
typeof(nint)
è typeof(IntPtr)
; typeof(nuint)
è typeof(UIntPtr)
.
sizeof(nint)
e sizeof(nuint)
sono supportati, ma richiedono la compilazione in un contesto non sicuro (come richiesto per sizeof(IntPtr)
e sizeof(UIntPtr)
).
I valori non sono costanti in fase di compilazione.
sizeof(nint)
viene implementato come sizeof(IntPtr)
anziché come IntPtr.Size
; sizeof(nuint)
viene implementato come sizeof(UIntPtr)
anziché come UIntPtr.Size
.
Diagnostica del compilatore per i riferimenti ai tipi che coinvolgono nint
o nuint
riportano nint
o nuint
piuttosto che IntPtr
o UIntPtr
.
Metadati
nint
e nuint
sono rappresentati nei metadati come System.IntPtr
e System.UIntPtr
.
I riferimenti di tipo che includono nint
o nuint
vengono generati con un System.Runtime.CompilerServices.NativeIntegerAttribute
per indicare quali parti del riferimento al tipo sono int nativi.
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;
}
}
La codifica dei riferimenti di tipo con NativeIntegerAttribute
è descritta in NativeIntegerAttribute.md.
Alternative
Un'alternativa all'approccio di cancellazione dei tipi precedente consiste nell'introdurre nuovi tipi: System.NativeInt
e System.NativeUInt
.
public readonly struct NativeInt
{
public IntPtr Value;
}
I tipi distinti consentirebbero l'overload distinto da IntPtr
e permetterebbero un'analisi distinta e ToString()
.
Tuttavia, ci sarebbe più lavoro per il CLR nel gestire questi tipi in modo efficiente, vanificando lo scopo principale della funzionalità: l'efficienza.
E l'interoperabilità con il codice int nativo esistente che usa IntPtr
sarebbe più difficile.
Un'altra alternativa consiste nell'aggiungere un supporto int più nativo per IntPtr
nel framework, ma senza supporto specifico del compilatore.
Tutte le nuove conversioni e le operazioni aritmetiche sarebbero supportate automaticamente dal compilatore.
Tuttavia, il linguaggio non fornisce parole chiave, costanti o operazioni di checked
.
Riunioni di design
- 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