Compartir a través de


IntPtr numérico

Nota:

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C#, en el artículo sobre especificaciones.

Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/6065

Resumen

Esta es una revisión de la característica inicial de enteros nativos (spec), donde los tipos nint/nuint eran distintos de los tipos subyacentes System.IntPtr/System.UIntPtr. En resumen, ahora tratamos nint/nuint como tipos simples aliasing System.IntPtr/System.UIntPtr, como hacemos para int en relación a System.Int32. El indicador de características de tiempo de ejecución System.Runtime.CompilerServices.RuntimeFeature.NumericIntPtr desencadena este nuevo comportamiento.

Diseño

8.3.5 Tipos simples

C# proporciona un conjunto de tipos struct predefinidos llamados tipos simples. Los tipos simples se identifican mediante palabras clave, pero estas palabras clave son simplemente alias de tipos predefinidos de struct en el espacio de nombres System, como se describe en la tabla siguiente.

palabra clave Tipo de alias
sbyte System.SByte
byte System.Byte
short System.Int16
ushort System.UInt16
int System.Int32
uint System.UInt32
nint System.IntPtr
nuint System.UIntPtr
long System.Int64
ulong System.UInt64
char System.Char
float System.Single
double System.Double
bool System.Boolean
decimal System.Decimal

[...]

8.3.6 Tipos integrales

C# admite once tipos integrales: sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong y char. [...]

8.8 Tipos no administrados

En otras palabras, un unmanaged_type es uno de los siguientes:

  • sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double, decimal o bool.
  • Cualquier enum_type.
  • Cualquier struct_type que no sea un tipo construido y contenga campos de unmanaged_types solamente.
  • En código inseguro, cualquier pointer_type.

10.2.3 Conversiones numéricas implícitas

Las conversiones numéricas implícitas son:

  • De sbyte a short, int, nint, long, float, double o decimal.
  • De byte a short, ushort, int, uint, nint, nuint, long, ulong, float, double o decimal.
  • De short a int, nint, long, float, double o decimal.
  • De ushort a int, uint, nint, nuint, long, ulong, float, double o decimal.
  • De int a nint, long, float, double o decimal.
  • De uint a nuint, long, ulong, float, double o decimal.
  • De nint a long, float, double o decimal.
  • De nuint a ulong, float, double o decimal.
  • De long a float, double o decimal.
  • De ulong a float, double o decimal.
  • De char a ushort, int, uint, nint, nuint, long, ulong, float, double o decimal.
  • De float a double.

[...]

10.2.11 Conversiones de expresiones constantes implícitas

Una conversión de expresión constante implícita permite las siguientes conversiones:

  • Una constant_expression de tipo int se puede convertir a tipo sbyte, byte, short, ushort, uint, nint, nuint o ulong, siempre que el valor de la constant_expression esté dentro del rango del tipo de destino. [...]

10.3.2 Conversiones numéricas explícitas

Las conversiones numéricas explícitas son las conversiones de un numeric_type a otro numeric_type para el que no existe ya una conversión numérica implícita:

  • De sbyte a byte, ushort, uint, nuint, ulong o char.
  • De byte a sbyte o char.
  • De short a sbyte, byte, ushort, uint, nuint, ulong o char.
  • De ushort a sbyte, byte, short o char.
  • De int a sbyte, byte, short, ushort, uint, nuint, ulong o char.
  • De uint a sbyte, byte, short, ushort, int, nint o char.
  • De long a sbyte, byte, short, ushort, int, uint, nint, nuint, ulong o char.
  • De nint a sbyte, byte, short, ushort, int, uint, nuint, ulong, o char.
  • De nuint a sbyte, byte, short, ushort, int, uint, nint, long, o char.
  • De ulong a sbyte, byte, short, ushort, int, uint, nint, nuint, long o char.
  • De char a sbyte, byte o short.
  • De float a sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char o decimal.
  • De double a sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float o decimal.
  • De decimal a sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float o double.

[...]

10.3.3 Conversiones de enumeración explícitas

Las conversiones explícitas de enumeración son:

  • Desde sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double o decimal a cualquier enum_type.
  • Desde cualquier enum_type a sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double o decimal.
  • De cualquier enum_type a cualquier otro enum_type.

12.6.4.7 Mejor objetivo de conversión

Dados dos tipos T₁ y T₂, T₁ es un mejor destino de conversión que T₂ si se cumple una de las siguientes condiciones:

  • Existe una conversión implícita de T₁ a T₂ y no existe ninguna conversión implícita de T₂ a T₁
  • T₁ es Task<S₁>, T₂ es Task<S₂> y S₁ es un objetivo de conversión mejor que S₂
  • T₁ es S₁ o S₁? donde S₁ es un tipo entero con signo y T₂ es S₂ o S₂? donde S₂ es un tipo entero sin signo. Específicamente: [...]

12.8.12 Acceso a elementos

[...] El número de expresiones de la argument_list será el mismo que el rango del array_type, y cada expresión será del tipo int, uint, nint, nuint, long o ulong, o será implícitamente convertible a uno o más de estos tipos.

11.8.12.2 Acceso a matrices

[...] El número de expresiones de la argument_list será el mismo que el rango del array_type, y cada expresión será del tipo int, uint, nint, nuint, long o ulong, o será implícitamente convertible a uno o más de estos tipos.

[...] El procesamiento en tiempo de ejecución de un acceso a una matriz de la forma P[A], donde P es una primary_no_array_creation_expression de un array_type y A es una argument_list, consta de los siguientes pasos: [...]

  • Las expresiones de índice de la argument_list se evalúan en orden, de izquierda a derecha. Tras la evaluación de cada expresión de índice, se realiza una conversión implícita a uno de los siguientes tipos: int, uint, nint, nuint, long, ulong. Se elige el primer tipo de la lista para el que existe una conversión implícita. [...]

12.8.16 Operadores de incremento y decremento postfijos

La resolución de sobrecarga de operador unario se aplica para seleccionar una implementación de operador específica. Existen operadores predefinidos ++ y -- para los siguientes tipos: sbyte, byte, short, ushort, int, uint, nint, nuint,long, ulong, char, float, double, decimal y cualquier tipo enum.

12.9.2 Operador de suma unario

Los operadores más unarios predefinidos son:

...
nint operator +(nint x);
nuint operator +(nuint x);

12.9.3 Operador de resta unario

Los operadores menos unarios predefinidos son:

  • Negación de enteros:

    ...
    nint operator –(nint x);
    

12.8.16 Operadores de incremento y decremento postfijos

Existen operadores predefinidos ++ y -- para los siguientes tipos: sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double, decimal, y cualquier tipo enum.

11.7.19 Expresiones de valor por defecto

Además, un default_value_expression es una expresión constante si el tipo es uno de los siguientes tipos de valor: sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double, decimal, bool, o cualquier tipo de enumeración.

12.9.5 Operador de complemento bit a bit

Los operadores de complemento a nivel de bit predefinidos son:

...
nint operator ~(nint x);
nuint operator ~(nuint x);

12.9.6 Operadores de incremento y decremento prefijos

Existen operadores predefinidos ++ y -- para los siguientes tipos: sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double, decimal, y cualquier tipo enum.

12.10 Operadores aritméticos

12.10.2 Operador de multiplicación

A continuación se enumeran los operadores de multiplicación predefinidos. Los operadores calculan el producto de x y y.

  • Multiplicación de enteros:

    ...
    nint operator *(nint x, nint y);
    nuint operator *(nuint x, nuint y);
    

12.10.3 Operador de división

A continuación se enumeran los operadores de división predefinidos. Todos los operadores calculan el cociente de x y y.

  • División entera:

    ...
    nint operator /(nint x, nint y);
    nuint operator /(nuint x, nuint y);
    

12.10.4 Operador de resto

A continuación se enumeran los operadores de resto predefinidos. Todos los operadores calculan el resto de la división entre x y y.

  • Resto entero:

    ...
    nint operator %(nint x, nint y);
    nuint operator %(nuint x, nuint y);
    

12.10.5 Operador de suma

  • Suma de números enteros:

    ...
    nint operator +(nint x, nint y);
    nuint operator +(nuint x, nuint y);
    

12.10.6 Operador de resta

  • Resta de números enteros:

    ...
    nint operator –(nint x, nint y);
    nuint operator –(nuint x, nuint y);
    

12.11 Operadores de desplazamiento

A continuación se enumeran los operadores de desplazamiento predefinidos.

  • Desplazamiento a la izquierda:

    ...
    nint operator <<(nint x, int count);
    nuint operator <<(nuint x, int count);
    
  • Desplazamiento a la derecha:

    ...
    nint operator >>(nint x, int count);
    nuint operator >>(nuint x, int count);
    

    El operador >> desplaza x hacia la derecha por un número de bits calculado como se describe a continuación.

    Cuando x es del tipo int, nint o long, los bits de orden inferior de x se descartan, los bits restantes se desplazan a la derecha y las posiciones de bits vacías de orden superior se ponen a cero si x es no negativo y a uno si x es negativo.

    Cuando x es de tipo uint, nuint o ulong, los bits de orden inferior de x se descartan, los bits restantes se desplazan a la derecha y las posiciones de bits vacíos de orden superior se ponen a cero.

  • Desplazamiento sin signo a la derecha:

    ...
    nint operator >>>(nint x, int count);
    nuint operator >>>(nuint x, int count);
    

Para los operadores predefinidos, el número de bits a desplazar se calcula como sigue: [...]

  • Cuando el tipo de x es nint o nuint, el número de bits a desplazar viene dado por los cinco bits de orden inferior de count en una plataforma de 32 bits, o los seis bits de orden inferior de count en una plataforma de 64 bits.

12.12 Operadores relacionales y de comprobación de tipo

12.12.2 Operadores de comparación de enteros

Los operadores de comparación de enteros predefinidos son:

...
bool operator ==(nint x, nint y);
bool operator ==(nuint x, nuint y);

bool operator !=(nint x, nint y);
bool operator !=(nuint x, nuint y);

bool operator <(nint x, nint y);
bool operator <(nuint x, nuint y);

bool operator >(nint x, nint y);
bool operator >(nuint x, nuint y);

bool operator <=(nint x, nint y);
bool operator <=(nuint x, nuint y);

bool operator >=(nint x, nint y);
bool operator >=(nuint x, nuint y);

12.12 Operadores lógicos

12.12.2 Operadores lógicos de enteros

Los operadores lógicos de enteros predefinidos son:

...
nint operator &(nint x, nint y);
nuint operator &(nuint x, nuint y);

nint operator |(nint x, nint y);
nuint operator |(nuint x, nuint y);

nint operator ^(nint x, nint y);
nuint operator ^(nuint x, nuint y);

12.22 Expresiones constantes

Una expresión constante puede ser de tipo valor o de tipo referencia. Si una expresión constante es un tipo de valor, debe ser uno de los siguientes tipos: sbyte, byte, short, ushort, int, uint, nint, nuint, long, ulong, char, float, double, decimal, bool, o cualquier tipo de enumeración.

[...]

Una conversión implícita de expresiones constantes permite convertir una expresión constante de tipo int a sbyte, byte, short, ushort, uint, nint, nuint, o ulong o , siempre que el valor de la expresión constante esté dentro del rango del tipo de destino.

17.4 Acceso a elementos del array

Para acceder a los elementos de una matriz se utilizan expresiones element_access de la forma A[I₁, I₂, ..., Iₓ], donde A es una expresión de tipo matriz y cada Iₑ es una expresión de tipo int, uint, nint, nuint,long, ulong o puede convertirse implícitamente a uno o varios de estos tipos. El resultado de un acceso a un elemento del array es una variable, a saber, el elemento del array seleccionado por los índices.

23.5 Conversiones de punteros

23.5.1 General

[...]

Además, en un contexto inseguro, el conjunto de conversiones explícitas disponibles se amplía para incluir las siguientes conversiones explícitas de punteros:

  • De cualquier tipo_de_puntero a cualquier otro tipo_de_puntero .
  • De sbyte, byte, short, ushort, int, uint, nint, nuint,long o ulong a cualquier pointer_type.
  • De cualquier pointer_type a sbyte, byte, short, ushort, int, uint, nint, nuint,long o ulong.

23.6.4 Acceso a elementos de puntero

[...] En un acceso a un elemento puntero de la forma P[E], P deberá ser una expresión de un tipo puntero distinto de void*, y E deberá ser una expresión que pueda ser convertida implícitamente a int, uint, nint, nuint,long o ulong.

23.6.7 Aritmética de punteros

En un contexto inseguro, el operador + y el operador pueden aplicarse a valores de todos los tipos de puntero excepto void*. Así, para cada tipo de puntero T*, los siguientes operadores están implícitamente definidos:

[...]
T* operator +(T* x, nint y);
T* operator +(T* x, nuint y);
T* operator +(nint x, T* y);
T* operator +(nuint x, T* y);
T* operator -(T* x, nint y);
T* operator -(T* x, nuint y);

Dada una expresión P de tipo puntero T* y una expresión N de tipo int, uint, nint, nuint,long o ulong, las expresiones P + N y N + P calculan el valor puntero de tipo T* que resulta de sumar N * sizeof(T) a la dirección dada por P. Del mismo modo, la expresión P – N calcula el valor del puntero de tipo T* que resulta de restar N * sizeof(T) de la dirección dada por P.

Consideraciones varias

Cambios importantes

Uno de los principales impactos de este diseño es que System.IntPtr y System.UIntPtr obtienen algunos operadores integrados (conversiones, unarios y binarios).
Entre ellos se incluyen operadores checked, que significa que los siguientes operadores de esos tipos ahora se iniciarán al desbordarse:

  • IntPtr + int
  • IntPtr - int
  • IntPtr -> int
  • long -> IntPtr
  • void* -> IntPtr

Codificación de metadatos

Este diseño significa que nint y nuint pueden emitirse simplemente como System.IntPtr y System.UIntPtr, sin utilizar System.Runtime.CompilerServices.NativeIntegerAttribute.
Del mismo modo, al cargar metadatos NativeIntegerAttribute puede ser omitido.