Partager via


Entiers de taille native

Remarque

Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.

Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).

Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .

Résumé

Prise en charge du langage pour des types d’entiers signés et non signés de taille native.

La motivation est pour les scénarios d’interopérabilité et pour les bibliothèques de bas niveau.

Création

Les identificateurs nint et nuint sont de nouveaux mots clés contextuels qui représentent des types entiers signés et non signés natifs. Les identificateurs sont traités uniquement comme des mots clés lorsque la recherche de noms ne trouve pas de résultat viable à cet emplacement du programme.

nint x = 3;
_ = nint.Equals(x, 3);

Les types nint et nuint sont représentés par les types sous-jacents System.IntPtr et System.UIntPtr avec le compilateur présentant des conversions et des opérations supplémentaires pour ces types en tant qu’ints natifs.

Constantes

Les expressions constantes peuvent être de type nint ou nuint. Il n’existe aucune syntaxe directe pour les littéraux int natifs. Les conversions implicites ou explicites d’autres valeurs constantes entières peuvent être utilisées à la place : const nint i = (nint)42;.

Les constantes nint sont comprises entre int.MinValueet int.MaxValue.

Les constantes nuint se trouvent dans l'intervalle [uint.MinValue, uint.MaxValue].

Il n’existe aucun champ MinValue ou MaxValue sur nint ou nuint car, à part nuint.MinValue, ces valeurs ne peuvent pas être émises en tant que constantes.

Le repli constant est pris en charge pour tous les opérateurs unaires { +, -, ~ } et les opérateurs binaires { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }. Les opérations de repli constantes sont évaluées avec des Int32 et des opérandes UInt32 plutôt que des ints natifs pour un comportement cohérent, quelle que soit la plateforme du compilateur. Si l’opération génère une valeur constante en 32 bits, le pliage constant est effectué au moment de la compilation. Sinon, l’opération est exécutée au moment de l’exécution et n’est pas considérée comme une constante.

Conversions

Il existe une conversion d’identité entre nint et IntPtr, et entre nuint et UIntPtr. Il existe une conversion d’identité entre des types composés qui diffèrent uniquement par des entiers natifs et leurs types sous-jacents : tableaux, Nullable<>, types construits et tuples.

Les tableaux ci-dessous couvrent les conversions entre les types spéciaux. (L'IL pour chaque conversion inclut les variantes pour les contextes unchecked et checked si elles diffèrent.)

Remarques générales sur le tableau ci-dessous :

  • conv.u est une conversion à extension par zéros vers un entier natif et conv.i est une conversion à extension de signe vers un entier natif.
  • les contextes checked pour widening et narrowing sont :
    • conv.ovf.* pour signed to *
    • conv.ovf.*.un pour unsigned to *
  • les contextes unchecked pour widening sont :
    • conv.i* pour signed to * (où * est la largeur cible)
    • conv.u* pour unsigned to * (où * est la largeur cible)
  • les contextes unchecked pour narrowing sont :
    • conv.i* pour any to signed * (où * est la largeur cible)
    • conv.u* pour any to unsigned * (où * est la largeur cible)

Prenons quelques exemples :

  • sbyte to nint et sbyte to nuint utilisent conv.i tandis que byte to nint et byte to nuint utilisent conv.u parce qu'ils sont tous élargissant.
  • nint to byte et nuint to byte utiliser conv.u1 tandis que nint to sbyte et nuint to sbyte utiliser conv.i1. Pour byte, sbyte, short, et ushort, le « type de pile » est int32. Ainsi, conv.i1 équivaut effectivement à « downcast to a signed byte and then sign-extend up to int32 » (converti en un octet signé, puis étendu le signe jusqu’à int32) tandis que conv.u1 équivaut effectivement à « downcast to an unsigned byte and then zero-extend up to int32 » (converti en un octet non signé, puis étendu avec zéro jusqu’à int32).
  • checked void* to nint utilise conv.ovf.i.un la même façon que checked void* to long utilise conv.ovf.i8.un.
Opérande Cible Conversion IL
object nint Déballage 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 Identité
UIntPtr nint Aucun
object nuint Déballage 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 Aucun
UIntPtr nuint Identité
Énumération nint ExplicitEnumeration
Énumération nuint ExplicitEnumeration
Opérande Cible Conversion IL
nint object Boxe box
nint void* PointerToVoid nop / conv.ovf.u
nint nuint ExplicitNumeric conv.u (peut être omis) / 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 Identité
nint UIntPtr Aucun
nint Énumération ExplicitEnumeration
nuint object Boxe box
nuint void* PointerToVoid Nop
nuint nint ExplicitNumeric conv.i(peut être omis) / 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 Aucun
nuint UIntPtr Identité
nuint Énumération ExplicitEnumeration

La conversion de A en Nullable<B> est la suivante :

  • une conversion nullable implicite s’il existe une conversion d’identité ou une conversion implicite de A en B ;
  • une conversion nullable explicite s’il existe une conversion explicite de A en B ;
  • sinon non valide.

La conversion de Nullable<A> en B est la suivante :

  • une conversion nullable explicite s’il existe une conversion d’identité ou une conversion numérique implicite ou explicite de A en B ;
  • sinon non valide.

La conversion de Nullable<A> en Nullable<B> est la suivante :

  • une conversion d’identité en cas de conversion d’identité de A en B;
  • une conversion nullable explicite s’il existe une conversion numérique implicite ou explicite de A en B ;
  • sinon non valide.

Opérateurs

Les opérateurs prédéfinis sont les suivants. Ces opérateurs sont considérés lors de la résolution de surcharge en fonction de règles normales pour les conversions implicites si au moins l’un des opérandes est de type nint ou nuint.

(Le code IL pour chaque opérateur inclut les variantes pour les contextes unchecked et checked si elles diffèrent.)

Unaire Signature d’opérateur 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
Binaire Signature d’opérateur 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

Pour certains opérateurs binaires, les opérateurs IL prennent en charge des types d’opérandes supplémentaires (consultez table de type d’opérande ECMA-335 III.1.5). Toutefois, l’ensemble des types d’opérandes pris en charge par C# est limité par souci de simplicité et de cohérence avec les opérateurs existants dans le langage.

Les versions « liftées » des opérateurs, où les arguments et les types de retour sont nint? et nuint?, sont prises en charge.

Les opérations d’affectation composée x op= yx ou y sont des ints natifs respectent les mêmes règles que les autres types primitifs avec des opérateurs prédéfinis. Plus précisément, l’expression est liée comme x = (T)(x op y)T est le type de x et où x n’est évalué qu’une seule fois.

Les opérateurs de décalage doivent masquer le nombre de bits à déplacer , à 5 bits si sizeof(nint) est 4, et à 6 bits si sizeof(nint) est de 8. (voir §12.11) en spécification C#).

Le compilateur C#9 signale des erreurs lors de la liaison aux opérateurs entiers natifs prédéfinis lors de la compilation avec une version antérieure du langage, mais permet l'utilisation de conversions prédéfinies en entiers natifs et à partir d'entiers natifs.

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

Arithmétique du pointeur

Il n’existe aucun opérateur prédéfini en C# pour l’ajout ou la soustraction de pointeur avec des décalages entiers natifs. Au lieu de cela, les valeurs nint et nuint sont promues en long et ulong et l’arithmétique du pointeur utilise des opérateurs prédéfinis pour ces types.

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)

Promotions numériques binaires

Le texte informatif binary numeric promotions (voir §12.4.7.3) dans la spécification C# est mis à jour comme suit :

  • Sinon, si l’opérande est de type ulong, l’autre opérande est converti en type ulong, ou une erreur de durée de liaison se produit si l’autre opérande est de type sbyte, short, int, nintou long.
  • Sinon, si l’opérande est de type nuint, l’autre opérande est converti en type nuint, ou une erreur de durée de liaison se produit si l’autre opérande est de type sbyte, short, int, nintou long.
  • Sinon, si l’un des opérandes est de type long, l’autre opérande est converti en type long.
  • Sinon, si l’opérande est de type uint et que l’autre opérande est de type sbyte, short, nint, ou int, les deux opérandes sont convertis en type long.
  • Sinon, si l’un des opérandes est de type uint, l’autre opérande est converti en type uint.
  • Sinon, si l’un des opérandes est de type nint, l’autre opérande est converti en type nint.
  • Sinon, les deux opérandes sont convertis au type int.

Dynamique

Les conversions et les opérateurs sont synthétisés par le compilateur et ne font pas partie des types IntPtr et UIntPtr sous-jacents. Par conséquent, ces conversions et opérateurs ne sont pas disponibles depuis le runtime binder pour 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'

Types de membres

Le seul constructeur pour nint ou nuint est le constructeur sans paramètre.

Les membres suivants de System.IntPtr et de System.UIntPtrsont explicitement exclus de nint ou de 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();

Les membres restants de System.IntPtr et de System.UIntPtrsont implicitement inclus dans nint et nuint. Pour .NET Framework 4.7.2 :

public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);

Les interfaces implémentées par System.IntPtr et System.UIntPtrsont implicitement incluses dans nint et nuint, avec des occurrences des types sous-jacents remplacés par les types entiers natifs correspondants. Par exemple, si IntPtr implémente ISerializable, IEquatable<IntPtr>, IComparable<IntPtr>, nint implémente ISerializable, IEquatable<nint>, IComparable<nint>.

Substitution, masquage et implémentation

nint et System.IntPtr, ainsi que nuint et System.UIntPtr, sont considérés comme équivalents pour la substitution, le masquage et l'implémentation.

Les surcharges ne peuvent pas différer uniquement par nint et System.IntPtr, ni par nuint et System.UIntPtr. Les substitutions et implémentations peuvent différer uniquement par nint et System.IntPtr, ou nuint et System.UIntPtr. Les méthodes masquent d'autres méthodes qui diffèrent seulement par nint et System.IntPtr, ou nuint et System.UIntPtr.

Divers

nint et les expressions nuint utilisées comme index de tableau sont émises sans conversion.

static object GetItem(object[] array, nint index)
{
    return array[index]; // ok
}

nint et nuint ne peuvent pas être utilisés comme type de base enum à partir de C#.

enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}

Les opérations de lecture et d’écriture sont atomiques pour nint et nuint.

Les champs peuvent être marqués volatile pour les types nint et nuint. ECMA-334 15.5.4 n’inclut pas enum avec le type de base System.IntPtr ou System.UIntPtr toutefois.

default(nint) et new nint() sont équivalents à (nint)0; default(nuint) et new nuint() sont équivalents à (nuint)0.

typeof(nint) est typeof(IntPtr); typeof(nuint) est typeof(UIntPtr).

sizeof(nint) et sizeof(nuint) sont pris en charge, mais nécessitent la compilation dans un contexte non sécurisé (comme requis pour sizeof(IntPtr) et sizeof(UIntPtr)). Les valeurs ne sont pas des constantes au moment de la compilation. sizeof(nint) est implémentée comme sizeof(IntPtr) plutôt que IntPtr.Size; sizeof(nuint) est implémentée en tant que sizeof(UIntPtr) plutôt que UIntPtr.Size.

Les diagnostics du compilateur pour les références de type impliquant nint ou nuint rapportent nint ou nuint plutôt que IntPtr ou UIntPtr.

Métadonnées

nint et nuint sont représentés dans les métadonnées sous forme de System.IntPtr et de System.UIntPtr.

Les références de type qui incluent nint ou nuint sont émises avec un System.Runtime.CompilerServices.NativeIntegerAttribute pour indiquer quelles parties de la référence de type sont des ints natifs.

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;
    }
}

L’encodage des références de type avec NativeIntegerAttribute est abordé dans NativeIntegerAttribute.md.

Alternatives

Une alternative à l’approche « effacement de type » ci-dessus consiste à introduire de nouveaux types : System.NativeInt et System.NativeUInt.

public readonly struct NativeInt
{
    public IntPtr Value;
}

Des types distincts permettraient une surcharge distincte de IntPtr et permettraient une analyse distincte et ToString(). Mais cela exigerait plus de travail de la part du CLR pour gérer ces types efficacement, ce qui contredit l'objectif principal de la fonctionnalité - l'efficacité. Et l’interopérabilité avec du code int natif existant qui utilise IntPtr serait plus difficile.

L’autre solution est d’ajouter plus de prise en charge des entiers natifs pour IntPtr dans le framework, sans prise en charge spécifique du compilateur. Toutes les nouvelles conversions et opérations arithmétiques sont prises en charge automatiquement par le compilateur. Toutefois, le langage ne fournit pas de mots clés, de constantes ou d’opérations checked.

Concevoir des réunions