Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
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 .
Problème de champion : https://github.com/dotnet/csharplang/issues/435
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 sont comprises entre uint.MinValueet 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.uest une conversion à extension par zéros vers un entier natif etconv.iest une conversion à extension de signe vers un entier natif. - les contextes
checkedpour widening et narrowing sont :-
conv.ovf.*poursigned to * -
conv.ovf.*.unpourunsigned to *
-
- les contextes
uncheckedpour widening sont :-
conv.i*poursigned to *(où * est la largeur cible) -
conv.u*pourunsigned to *(où * est la largeur cible)
-
- les contextes
uncheckedpour 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 nintetsbyte to nuintutilisentconv.itandis quebyte to nintetbyte to nuintutilisentconv.uparce qu'ils sont tous élargissant. -
nint to byteetnuint to byteutiliserconv.u1tandis quenint to sbyteetnuint to sbyteutiliserconv.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 nintutiliseconv.ovf.i.unla même façon quechecked void* to longutiliseconv.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
AenB; - une conversion nullable explicite s’il existe une conversion explicite de
AenB; - 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
AenB; - 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
AenB; - une conversion nullable explicite s’il existe une conversion numérique implicite ou explicite de
AenB; - 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,nintoulong.- 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,nintoulong.- 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
uintet 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.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
- 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