Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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 IntPtre 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. -
checkedcontesti sia per allargamento che per restrizione:-
conv.ovf.*persigned to * -
conv.ovf.*.unperunsigned to *
-
-
uncheckedcontesti per l'ampliamento di sono:-
conv.i*persigned to *(dove * è la larghezza di destinazione) -
conv.u*perunsigned to *(dove * è la larghezza di destinazione)
-
-
uncheckedcontesti 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 nintesbyte to nuintusanoconv.i, mentrebyte to nintebyte to nuintusanoconv.uperché sono tutti che ampliano. -
nint to byteenuint to byteusareconv.u1mentrenint to sbyteenuint to sbyteusareconv.i1. Perbyte,sbyte,shorteushortil "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 nintusaconv.ovf.i.unallo stesso modo in cuichecked void* to longusaconv.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
AaB; - una conversione esplicita "nullable" se è presente una conversione esplicita da
AaB; - 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
AaB; - in caso contrario, non è valido.
La conversione da Nullable<A> a Nullable<B> è:
- una conversione di identità se è presente una conversione di identità da
AaB; - una conversione esplicita annullabile se è presente una conversione numerica implicita o esplicita da
AaB; - 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 tipoulongo si verifica un errore di binding se l'altro operando è di tiposbyte,short,int,nintolong.- In caso contrario, se uno degli operandi è di tipo
nuint, l'altro operando viene convertito in tiponuinto si verifica un errore di binding se l'altro operando è di tiposbyte,short,int,nintolong.- 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
uinte 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.UIntPtrvengono 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.UIntPtrvengono 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.UIntPtrsono 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