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.MinValue
et 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 etconv.i
est une conversion à extension de signe vers un entier natif. - les contextes
checked
pour widening et narrowing sont :-
conv.ovf.*
poursigned to *
-
conv.ovf.*.un
pourunsigned to *
-
- les contextes
unchecked
pour widening sont :conv.i*
poursigned to *
(où * est la largeur cible)conv.u*
pourunsigned to *
(où * est la largeur cible)
- les contextes
unchecked
pour narrowing sont :conv.i*
pourany to signed *
(où * est la largeur cible)conv.u*
pourany to unsigned *
(où * est la largeur cible)
Prenons quelques exemples :
sbyte to nint
etsbyte to nuint
utilisentconv.i
tandis quebyte to nint
etbyte to nuint
utilisentconv.u
parce qu'ils sont tous élargissant.nint to byte
etnuint to byte
utiliserconv.u1
tandis quenint to sbyte
etnuint to sbyte
utiliserconv.i1
. Pourbyte
,sbyte
,short
, etushort
, le « type de pile » estint32
. 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 queconv.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
utiliseconv.ovf.i.un
la même façon quechecked void* to long
utiliseconv.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
enB
; - une conversion nullable explicite s’il existe une conversion explicite de
A
enB
; - 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
enB
; - 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
enB
; - une conversion nullable explicite s’il existe une conversion numérique implicite ou explicite de
A
enB
; - 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= y
où x
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)
où 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 typeulong
, ou une erreur de durée de liaison se produit si l’autre opérande est de typesbyte
,short
,int
,nint
oulong
.- Sinon, si l’opérande est de type
nuint
, l’autre opérande est converti en typenuint
, ou une erreur de durée de liaison se produit si l’autre opérande est de typesbyte
,short
,int
,nint
oulong
.- Sinon, si l’un des opérandes est de type
long
, l’autre opérande est converti en typelong
.- Sinon, si l’opérande est de type
uint
et que l’autre opérande est de typesbyte
,short
,nint
, ouint
, les deux opérandes sont convertis en typelong
.- Sinon, si l’un des opérandes est de type
uint
, l’autre opérande est converti en typeuint
.- Sinon, si l’un des opérandes est de type
nint
, l’autre opérande est converti en typenint
.- 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.UIntPtr
sont 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.UIntPtr
sont 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.UIntPtr
sont 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
- 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