Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .
Questão campeã: https://github.com/dotnet/csharplang/issues/435
Resumo
Suporte para tipos de inteiros assinados e não assinados de tamanho nativo em linguagens de programação.
A motivação é para cenários de interoperabilidade e para bibliotecas de baixo nível.
Desenho
Os identificadores nint e nuint são novas palavras-chave contextuais que representam tipos inteiros nativos assinados e não assinados.
Os identificadores só são tratados como palavras-chave quando a pesquisa de nomes 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á uma sintaxe direta para literais do tipo inteiro nativo (int). Em vez disso, podem ser usados moldes implícitos ou explícitos de outros valores constantes integrais: const nint i = (nint)42;.
As constantes nint estão no intervalo [ int.MinValue, int.MaxValue ].
As constantes nuint 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.
O dobramento constante é suportado para todos os operadores unários { +, -, ~ } e operadores binários { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }.
As operações de dobramento constante são avaliadas com operandos Int32 e UInt32 em vez de ints nativos para um comportamento consistente, independentemente da plataforma do compilador.
Se a operação resultar em um valor constante em 32 bits, o dobramento constante será executado em tempo de compilação.
Caso contrário, a operação é executada em tempo de execução e não é 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 inteiros nativos e tipos subjacentes: matrizes, Nullable<>, tuplas e tipos construídos.
As tabelas abaixo abrangem as conversões entre tipos especiais.
(A IL para cada conversão inclui as variantes para os contextos unchecked e checked, se diferentes.)
Notas gerais sobre o quadro abaixo:
-
conv.ué uma conversão com extensão de zero para inteiro nativo econv.ié uma conversão com extensão de sinal para inteiro nativo. -
checkedcontextos para alargamento e estreitamento são:-
conv.ovf.*parasigned to * -
conv.ovf.*.unparaunsigned to *
-
-
uncheckedcontextos para alargamento são:-
conv.i*parasigned to *(onde * é a largura de destino) -
conv.u*paraunsigned to *(onde * é a largura de destino)
-
-
uncheckedcontextos para estreitamento são:-
conv.i*paraany to signed *(onde * é a largura de destino) -
conv.u*paraany to unsigned *(onde * é a largura de destino)
-
Dando alguns exemplos:
-
sbyte to nintesbyte to nuintusamconv.ienquantobyte to nintebyte to nuintusamconv.uporque são todos alargamento. -
nint to byteenuint to byteusamconv.u1enquantonint to sbyteenuint to sbyteusamconv.i1. Parabyte,sbyte,shorteushort, o "tipo de pilha" éint32. Assim,conv.i1é efetivamente "downcast para um byte assinado e, em seguida, sign-extend até int32", enquantoconv.u1é efetivamente "downcast para um byte não assinado e, em seguida, zero-extend até int32". -
checked void* to nintusaconv.ovf.i.unda mesma forma quechecked void* to longusaconv.ovf.i8.un.
| Operando | Público-alvo | Conversão | IL |
|---|---|---|---|
object |
nint |
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 |
Unboxing | unbox |
void* |
nuint |
PointerToVoid | Não. |
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 |
Enumeração Explícita | |
| Enumeração | nuint |
Enumeração Explícita |
| Operando | Público-alvo | Conversão | IL |
|---|---|---|---|
nint |
object |
Boxe | 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 | Enumeração Explícita | |
nuint |
object |
Boxe | box |
nuint |
void* |
PointerToVoid | Não. |
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 | Enumeração Explícita |
A conversão de A para Nullable<B> é:
- uma conversão implícita passível de nulidade se houver uma conversão de identidade ou uma 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, inválido.
A conversão de Nullable<A> para B é:
- uma conversão explícita anulável se houver uma conversão de identidade ou uma conversão numérica implícita ou explícita de
AparaB; - caso contrário, inválido.
A conversão de Nullable<A> para Nullable<B> é:
- uma conversão de identidade, se houver uma conversão de identidade de
AparaB; - uma conversão anulável explícita se houver uma conversão numérica implícita ou explícita de
AparaB; - caso contrário, 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 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 suportam tipos de operando adicionais (ver tabela de tipos de operando ECMA-335 III.1.5). Mas o conjunto de tipos de operando suportados pelo C# é limitado para simplicidade e consistência com operadores existentes na linguagem.
Versões elevadas dos operadores, onde os argumentos e os tipos de retorno são nint? e nuint?, são suportadas.
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 está ligada como x = (T)(x op y) onde T é o tipo de x e onde x é avaliada apenas uma vez.
Os operadores de deslocamento devem mascarar o número de bits em deslocamento - para 5 bits se sizeof(nint) for 4 e para 6 bits se sizeof(nint) for 8.
(ver §12.11) em C# spec).
O compilador C#9 relatará erros de vinculação a operadores inteiros nativos predefinidos ao compilar com uma versão de idioma 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 deslocamentos inteiros nativos.
Em vez disso, os valores nint e nuint são promovidos para long e ulong e a aritmética de ponteiro 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 promoções numéricas binárias texto informativo (ver §12.4.7.3) em C# spec) é atualizado da seguinte forma:
- …
- Caso contrário, se um operando for do tipo
ulong, o outro operando será convertido para o tipoulong, ou ocorrerá um erro de tempo de ligação se o outro operando for do tiposbyte,short,int,nintoulong.- Caso contrário, se um operando for do tipo
nuint, o outro operando será convertido em tiponuint, ou ocorrerá um erro de tempo de ligação se o outro operando for do tiposbyte,short,int,nintoulong.- Caso contrário, se um operando for do tipo
long, o outro operando será convertido em tipolong.- Caso contrário, se um operando for do tipo
uinte o outro operando for do tiposbyte,short,nint, ouint, ambos os operandos serão convertidos para o tipolong.- Caso contrário, se um operando for do tipo
uint, o outro operando será convertido em tipouint.- Caso contrário, se um operando for do tipo
nint, o outro operando será convertido em tiponint.- Caso contrário, ambos os operandos são convertidos para o tipo
int.
Dinâmica
As conversões e 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 do fichário de tempo de execução 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 do tipo
O único construtor para nint ou nuint é o construtor sem parâmetros.
Os seguintes membros da System.IntPtr e System.UIntPtrestão explicitamente excluídos da 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 no nint e nuint. Para o .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 implicitamente incluídas em nint e nuint, com ocorrências dos tipos subjacentes substituídas 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 implementar
nint e System.IntPtr, assim como nuint e System.UIntPtr, são considerados equivalentes para substituição, ocultação e implementação.
As sobrecargas não podem diferir apenas por nint e System.IntPtr, e nuint e System.UIntPtr.
Substituições e implementações podem diferir por nint e System.IntPtr, ou nuint e System.UIntPtr, sozinhos.
Os métodos ocultam outros métodos que diferem apenas por nint e System.IntPtr, ou nuint e System.UIntPtr.
Diversos
nint e nuint expressões 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 do 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) são suportados, mas requerem compilação em um contexto inseguro (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) e não como IntPtr.Size; sizeof(nuint) é implementado como sizeof(UIntPtr) e não como UIntPtr.Size.
Diagnósticos do compilador para referências de tipo envolvendo 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.
As 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 inteiros 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 tipos" acima é introduzir novos tipos: System.NativeInt e System.NativeUInt.
public readonly struct NativeInt
{
public IntPtr Value;
}
Tipos distintos permitiriam sobrecarga distinta de IntPtr e permitiriam uma análise sintática distinta de ToString().
Mas haveria mais trabalho para o CLR lidar com esses tipos de forma eficiente, o que derrota o objetivo principal 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 int nativo para IntPtr no framework, mas sem qualquer suporte específico ao compilador.
Quaisquer novas conversões e operações aritméticas seriam suportadas pelo compilador automaticamente.
Mas a linguagem não forneceria palavras-chave, constantes ou operações 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