Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .
Problema do especialista: https://github.com/dotnet/csharplang/issues/435
Resumo
Suporte de linguagem para tipos de inteiros assinados e não assinados de tamanho nativo.
A motivação é para cenários de interoperabilidade e para bibliotecas de baixo nível.
Design
Os identificadores nint e nuint são novas palavras-chave contextuais que representam tipos inteiros nativos com e sem sinal.
Os identificadores são tratados apenas como palavras-chave quando a pesquisa de nome não encontra um resultado viável nesse local do programa.
nint x = 3;
_ = nint.Equals(x, 3);
Os tipos nint e nuint são representados pelos tipos subjacentes System.IntPtr e System.UIntPtr com o compilador apresentando conversões e operações adicionais para esses tipos como ints nativos.
Constantes
As expressões constantes podem ser do tipo nint ou nuint.
Não há sintaxe direta para inteiros literais nativos. Em vez disso, podem ser usadas conversões implícitas ou explícitas de outros valores integrais constantes: const nint i = (nint)42;.
Constantesnint estão no intervalo [ int.MinValue, int.MaxValue ].
Constantesnuint estão no intervalo [ uint.MinValue, uint.MaxValue ].
Não há campos MinValue ou MaxValue em nint ou nuint porque, além de nuint.MinValue, esses valores não podem ser emitidos como constantes.
Há suporte para dobragem constante para todos os operadores unários { +, -, ~ } e operadores binários { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }.
As operações de dobragem constante são avaliadas com os operandos Int32 e UInt32 em vez de ints nativos para comportamento consistente, independentemente da plataforma do compilador.
Se a operação resultar em um valor constante de 32 bits, a dobragem constante é realizado no tempo de compilação.
Caso contrário, a operação será executada em runtime e não será considerada uma constante.
Conversões
Há uma conversão de identidade entre nint e IntPtr e entre nuint e UIntPtr.
Há uma conversão de identidade entre tipos compostos que diferem apenas por ints nativos e tipos subjacentes: matrizes, Nullable<>, tipos construídos e tuplas.
As tabelas abaixo abrangem as conversões entre tipos especiais.
(O IL para cada conversão inclui as variantes para os contextos unchecked e checked, se forem diferentes.)
Observações gerais sobre a tabela abaixo:
-
conv.ué uma conversão de extensão zero para inteiro nativo econv.ié uma conversão com extensão de sinal para um inteiro nativo. - Contextos
checkedpara ambos expansão e estreitamento são:-
conv.ovf.*parasigned to * -
conv.ovf.*.unparaunsigned to *
-
- Contextos
uncheckedpara expansão são:-
conv.i*parasigned to *(em que * é a largura de destino) -
conv.u*paraunsigned to *(em que * é a largura de destino)
-
- Contextos
uncheckedpara estreitamento são:-
conv.i*paraany to signed *(em que * é a largura de destino) -
conv.u*paraany to unsigned *(em que * é a largura de destino)
-
Seguindo alguns exemplos:
-
sbyte to nintesbyte to nuintusamconv.ienquantobyte to nintebyte to nuintusamconv.uporque todos eles são de expansão. -
nint to byteenuint to byteusamconv.u1enquantonint to sbyteenuint to sbyteusamconv.i1. Parabyte,sbyte,shorteushorto "tipo de pilha" éint32. Portanto,conv.i1é efetivamente "downcast para um byte assinado e, em seguida, estender o sinal até int32", enquantoconv.u1é efetivamente "downcast para um byte sem sinal e, em seguida, extensão zero até int32". -
checked void* to nintusaconv.ovf.i.unda mesma maneira quechecked void* to longusaconv.ovf.i8.un.
| Operando | Destino | Conversão | IL |
|---|---|---|---|
object |
nint |
Conversão unboxing | 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 |
Identidade | |
UIntPtr |
nint |
Nenhum | |
object |
nuint |
Conversão unboxing | 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 |
Nenhum | |
UIntPtr |
nuint |
Identidade | |
| Enumeração | nint |
ExplicitEnumeration | |
| Enumeração | nuint |
ExplicitEnumeration |
| Operando | Destino | Conversão | IL |
|---|---|---|---|
nint |
object |
Conversão boxing | box |
nint |
void* |
PointerToVoid | Nop/ conv.ovf.u |
nint |
nuint |
ExplicitNumeric |
conv.u (pode ser omitido) / 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 |
Identidade | |
nint |
UIntPtr |
Nenhum | |
nint |
Enumeração | ExplicitEnumeration | |
nuint |
object |
Conversão boxing | box |
nuint |
void* |
PointerToVoid | nop |
nuint |
nint |
ExplicitNumeric |
conv.i (pode ser omitido) / 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 |
Nenhum | |
nuint |
UIntPtr |
Identidade | |
nuint |
Enumeração | ExplicitEnumeration |
A conversão de A em Nullable<B> é:
- uma conversão implícita anulável se houver uma conversão de identidade ou conversão implícita de
AparaB; - uma conversão anulável explícita se houver uma conversão explícita de
AparaB; - caso contrário, será inválido.
A conversão de Nullable<A> em B é:
- uma conversão nula explícita se houver uma conversão de identidade ou conversão numérica implícita ou explícita de
AemB; - caso contrário, será inválido.
A conversão de Nullable<A> em Nullable<B> é:
- uma conversão de identidade se houver uma conversão de identidade de
AemB; - uma conversão nula explícita se houver uma conversão numérica implícita ou explícita de
AemB; - caso contrário, será inválido.
Operadores
Os operadores predefinidos são os seguintes.
Esses operadores são considerados durante a resolução de sobrecarga com base em regras normais para conversões implícitas se pelo menos um dos operandos for do tipo nint ou nuint.
(O IL para cada operador inclui as variantes para os contextos unchecked e checked, se forem diferentes.)
| Unário | Assinatura do Operador | 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ário | Assinatura do Operador | 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 |
Para alguns operadores binários, os operadores IL dão suporte a tipos de operando adicionais (consulte tabela de tipos de operandos ECMA-335 III.1.5). No entanto, o conjunto de tipos de operando com suporte pelo C# é limitado para simplicidade e consistência com operadores existentes na linguagem.
Há suporte para versões levantadas dos operadores, em que os argumentos e os tipos de retorno são nint? e nuint?.
As operações de atribuição composta x op= y em que x ou y são ints nativos seguem as mesmas regras que com outros tipos primitivos com operadores predefinidos.
Especificamente, a expressão é associada como x = (T)(x op y) em que T é o tipo de x e onde x só é avaliado uma vez.
Os operadores de deslocamento devem mascarar o número de bits a serem deslocados, para 5 bits se sizeof(nint) for 4 e para 6 bits se sizeof(nint) for 8.
(consulte §12.11) na especificação C#).
O compilador C#9 relatará erros de associação a operadores inteiros nativos predefinidos ao compilar com uma versão de linguagem anterior, mas permitirá o uso de conversões predefinidas de e para inteiros nativos.
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
}
}
Aritmética do ponteiro
Não há operadores predefinidos em C# para adição ou subtração de ponteiro com compensações de inteiros nativos.
Em vez disso, os valores nint e nuint são promovidos a long e ulong, e a aritmética de ponteiros usa operadores predefinidos para esses tipos.
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)
Promoções numéricas binárias
O texto informativo das promoções numéricas binárias (consulte §12.4.7.3) na especificação C#) é atualizado da seguinte forma:
- …
- Caso contrário, se o operando for do tipo
ulong, o outro operando será convertido para o tipoulongou ocorrerá um erro de tempo de associação se o outro operando for do tiposbyte,short,int,nintoulong.- Caso contrário, se o operando for do tipo
nuint, o outro operando será convertido para o tiponuintou ocorrerá um erro de tempo de associação se o outro operando for do tiposbyte,short,int,nintoulong.- Caso contrário, se qualquer operando for do tipo
long, o outro operando será convertido para o tipolong.- Caso contrário, se o operando for do tipo
uinte o outro operando for do tiposbyte,short,nint, ouint, os dois operandos serão convertidos para o tipolong.- Caso contrário, se qualquer operando for do tipo
uint, o outro operando será convertido para o tipouint.- Caso contrário, se qualquer operando for do tipo
nint, o outro operando será convertido para o tiponint.- Caso contrário, os dois operandos serão convertidos no tipo
int.
Dinâmico
As conversões e os operadores são sintetizados pelo compilador e não fazem parte dos tipos de IntPtr e UIntPtr subjacentes.
Como resultado, essas conversões e operadores não estão disponíveis a partir do associador de runtime para 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'
Membros de tipos
O único construtor para nint ou nuint é o construtor sem parâmetro.
Os seguintes membros de System.IntPtr e System.UIntPtrsão excluídos explicitamente de nint ou 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();
Os membros restantes de System.IntPtr e System.UIntPtrestão implicitamente incluídos em nint e nuint. Para .NET Framework 4.7.2:
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);
As interfaces implementadas por System.IntPtr e System.UIntPtrsão incluídas implicitamente em nint e nuint, com ocorrências dos tipos subjacentes substituídos pelos tipos inteiros nativos correspondentes.
Por exemplo, se IntPtr implementa ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>, então nint implementa ISerializable, IEquatable<nint>, IComparable<nint>.
Substituir, ocultar e implantar
nint, System.IntPtr, nuint e System.UIntPtr, são considerados equivalentes para substituir, ocultar e implantar.
As sobrecargas não podem ser diferentes apenas por nint, System.IntPtr, nuint e System.UIntPtr.
As substituições e implantações podem diferir apenas por nint e System.IntPtr ou nuint e System.UIntPtr.
Os métodos ocultam outros métodos que diferem apenas por nint e System.IntPtr, ou nuint e System.UIntPtr.
Diversos
As expressões nint e nuint usadas como índices de matriz são emitidas sem conversão.
static object GetItem(object[] array, nint index)
{
return array[index]; // ok
}
nint e nuint não podem ser usados como um tipo base enum de C#.
enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}
Leituras e gravações são atômicas para nint e nuint.
Os campos podem ser marcados volatile para os tipos nint e nuint.
No entanto, ECMA-334 15.5.4 não inclui enum com tipo base System.IntPtr ou System.UIntPtr.
default(nint) e new nint() são equivalentes a (nint)0; default(nuint) e new nuint() são equivalentes a (nuint)0.
typeof(nint) é typeof(IntPtr); typeof(nuint) é typeof(UIntPtr).
sizeof(nint) e sizeof(nuint) têm suporte, mas exigem compilação em um contexto não seguro (conforme necessário para sizeof(IntPtr) e sizeof(UIntPtr)).
Os valores não são constantes de tempo de compilação.
sizeof(nint) é implementado como sizeof(IntPtr) em vez de IntPtr.Size; sizeof(nuint) é implementado como sizeof(UIntPtr) em vez de UIntPtr.Size.
Diagnósticos do compilador para referências de tipo que envolvem nint ou nuint relatam nint ou nuint em vez de IntPtr ou UIntPtr.
Metadados
nint e nuint são representados em metadados como System.IntPtr e System.UIntPtr.
Referências de tipo que incluem nint ou nuint são emitidas com um System.Runtime.CompilerServices.NativeIntegerAttribute, para indicar quais partes da referência de tipo são ints nativos.
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;
}
}
A codificação de referências de tipo com NativeIntegerAttribute é abordada em NativeIntegerAttribute.md.
Alternativas
Uma alternativa à abordagem de "eliminação de tipo" acima é introduzir novos tipos: System.NativeInt e System.NativeUInt.
public readonly struct NativeInt
{
public IntPtr Value;
}
Tipos distintos permitiriam sobrecargas distintas de IntPtr e permitiriam análises distintas e ToString().
Mas haveria mais trabalho para o CLR lidar com esses tipos com eficiência, o que derrota a principal finalidade do recurso - a eficiência.
E a interoperabilidade com o código int nativo existente que usa IntPtr seria mais difícil.
Outra alternativa é adicionar mais suporte de int nativo para IntPtr na estrutura, mas sem qualquer suporte específico do compilador.
Quaisquer novas conversões e operações aritméticas serão suportadas pelo compilador automaticamente.
Mas o idioma não forneceria palavras-chave, constantes ou operações de checked.
Reuniões de 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