Inteiros de tamanho nativo
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 .
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. -
checked
contextos para alargamento e estreitamento são:-
conv.ovf.*
parasigned to *
-
conv.ovf.*.un
paraunsigned to *
-
-
unchecked
contextos para alargamento são:-
conv.i*
parasigned to *
(onde * é a largura de destino) -
conv.u*
paraunsigned to *
(onde * é a largura de destino)
-
-
unchecked
contextos 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 nint
esbyte to nuint
usamconv.i
enquantobyte to nint
ebyte to nuint
usamconv.u
porque são todos alargamento. -
nint to byte
enuint to byte
usamconv.u1
enquantonint to sbyte
enuint to sbyte
usamconv.i1
. Parabyte
,sbyte
,short
eushort
, 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 nint
usaconv.ovf.i.un
da mesma forma quechecked void* to long
usaconv.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 |
ApontadorParaVoid | 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* |
ApontadorParaVoid | 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
A
paraB
; - uma conversão anulável explícita se houver uma conversão explícita de
A
paraB
; - 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
A
paraB
; - 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
A
paraB
; - uma conversão anulável explícita se houver uma conversão numérica implícita ou explícita de
A
paraB
; - 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 | Illinois |
---|---|---|
+ |
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
,nint
oulong
.- 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
,nint
oulong
.- 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
uint
e 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.UIntPtr
estã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.UIntPtr
estã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.UIntPtr
sã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