Ler em inglês

Compartilhar via


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. 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.

C#
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 e conv.i é uma conversão com extensão de sinal para um inteiro nativo.
  • Contextoschecked para ambos expansão e estreitamento são:
    • conv.ovf.* para signed to *
    • conv.ovf.*.un para unsigned to *
  • Contextosunchecked para expansão são:
    • conv.i* para signed to * (em que * é a largura de destino)
    • conv.u* para unsigned to * (em que * é a largura de destino)
  • Contextosunchecked para estreitamento são:
    • conv.i* para any to signed * (em que * é a largura de destino)
    • conv.u* para any to unsigned * (em que * é a largura de destino)

Seguindo alguns exemplos:

  • sbyte to nint e sbyte to nuint usam conv.i enquanto byte to nint e byte to nuint usam conv.u porque todos eles são de expansão.
  • nint to byte e nuint to byte usam conv.u1 enquanto nint to sbyte e nuint to sbyte usam conv.i1. Para byte, sbyte, short e ushort o "tipo de pilha" é int32. Portanto, conv.i1 é efetivamente "downcast para um byte assinado e, em seguida, estender o sinal até int32", enquanto conv.u1 é efetivamente "downcast para um byte sem sinal e, em seguida, extensão zero até int32".
  • checked void* to nint usa conv.ovf.i.un da mesma maneira que checked void* to long usa conv.ovf.i8.un.
Operando Destino Conversão Illinois
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 Illinois
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 A para B;
  • uma conversão anulável explícita se houver uma conversão explícita de A para B;
  • 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 A em B;
  • 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 A em B;
  • uma conversão nula explícita se houver uma conversão numérica implícita ou explícita de A em B;
  • 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 Illinois
+ 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 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

C#
public class A
{
    public static nint F;
}

csc -langversion:8 -r:A.dll B.cs

C#
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.

C#
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 tipo ulong ou ocorrerá um erro de tempo de associação se o outro operando for do tipo sbyte, short, int, nint ou long.
  • Caso contrário, se o operando for do tipo nuint, o outro operando será convertido para o tipo nuint ou ocorrerá um erro de tempo de associação se o outro operando for do tipo sbyte, short, int, nintou long.
  • Caso contrário, se qualquer operando for do tipo long, o outro operando será convertido para o tipo long.
  • Caso contrário, se o operando for do tipo uint e o outro operando for do tipo sbyte, short, nint, ou int, os dois operandos serão convertidos para o tipo long.
  • Caso contrário, se qualquer operando for do tipo uint, o outro operando será convertido para o tipo uint.
  • Caso contrário, se qualquer operando for do tipo nint, o outro operando será convertido para o tipo nint.
  • 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.

C#
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.

C#
// 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:

C#
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.

C#
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#.

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.

C#
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.

C#
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