8 types
8.1 Général
Les types du langage C# sont divisés en deux catégories principales : les types de référence et les types valeur. Les types valeur et les types de référence peuvent être des types génériques, qui prennent un ou plusieurs paramètres de type. Les paramètres de type peuvent désigner les types valeur et les types référence.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) est disponible uniquement dans le code non sécurisé (§23).
Les types valeur diffèrent des types de référence dans ces variables des types valeur contiennent directement leurs données, tandis que les variables des types de référence stockent des références à leurs données, ce dernier étant appelé objets. Avec les types de référence, il est possible que deux variables référencent le même objet et, par conséquent, que les opérations sur une variable affectent l’objet référencé par l’autre variable. Avec les types valeur, les variables ont chacune leur propre copie des données, et il n’est pas possible d’effectuer des opérations sur l’une d’elles pour affecter l’autre.
Remarque : Lorsqu’une variable est une référence ou un paramètre de sortie, elle n’a pas son propre stockage, mais fait référence au stockage d’une autre variable. Dans ce cas, la variable ref ou out est effectivement un alias pour une autre variable et non une variable distincte. Note de fin
Le système de type C# est unifié afin qu’une valeur de n’importe quel type puisse être traitée comme un objet. Chaque type dans C# dérive directement ou indirectement du type object
, et object
est la classe de base fondamentale de tous les types. Les valeurs des types référence sont considérées comme des objets simplement en affichant les valeurs en tant que type object
. Les valeurs des types valeur sont traitées comme des objets en effectuant des opérations boxing et unboxing (§8.3.13).
Pour plus de commodité, tout au long de cette spécification, certains noms de types de bibliothèque sont écrits sans utiliser leur qualification de nom complet. Pour plus d’informations, consultez le §C.5 .
8.2 Types de référence
8.2.1 Général
Un type référence est un type de classe, un type d’interface, un type de tableau, un type délégué ou le dynamic
type. Pour chaque type de référence non nullable, il existe un type de référence nullable correspondant noté en ajoutant le ?
nom du type.
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type est disponible uniquement dans le code non sécurisé (§23.3). nullable_reference_type est abordée plus loin dans le §8.9.
Une valeur de type référence est une référence à une instance du type, celle-ci appelée objet. La valeur null
spéciale est compatible avec tous les types de référence et indique l’absence d’une instance.
Types de classes 8.2.2
Un type de classe définit une structure de données qui contient des membres de données (constantes et champs), des membres de fonction (méthodes, propriétés, événements, indexeurs, opérateurs, constructeurs d’instances, finaliseurs et constructeurs statiques) et des types imbriqués. Les types de classes prennent en charge l’héritage, un mécanisme permettant aux classes dérivées d’étendre et de spécialiser les classes de base. Les instances des types de classes sont créées à l’aide de object_creation_expressions (§12.8.17.2).
Les types de classes sont décrits dans le §15.
Certains types de classes prédéfinis ont une signification particulière dans le langage C#, comme décrit dans le tableau ci-dessous.
Type de classe | Description |
---|---|
System.Object |
Classe de base ultime de tous les autres types. Consultez le §8.2.3. |
System.String |
Type de chaîne du langage C#. Consultez le §8.2.5. |
System.ValueType |
Classe de base de tous les types valeur. Consultez le §8.3.2. |
System.Enum |
Classe de base de tous les enum types. Voir §19.5. |
System.Array |
Classe de base de tous les types de tableaux. Consultez le §17.2.2. |
System.Delegate |
Classe de base de tous les delegate types. Voir §20.1. |
System.Exception |
Classe de base de tous les types d’exceptions. Voir §21.3. |
8.2.3 Type d’objet
Le object
type de classe est la classe de base ultime de tous les autres types. Chaque type en C# dérive directement ou indirectement du object
type de classe.
Le mot clé object
est simplement un alias pour la classe System.Object
prédéfinie.
8.2.4 Type dynamique
Le dynamic
type, tel que object
, peut référencer n’importe quel objet. Lorsque les opérations sont appliquées aux expressions de type dynamic
, leur résolution est différée jusqu’à ce que le programme soit exécuté. Par conséquent, si l’opération ne peut pas être appliquée légitimement à l’objet référencé, aucune erreur n’est donnée pendant la compilation. Au lieu de cela, une exception est levée lorsque la résolution de l’opération échoue au moment de l’exécution.
Le dynamic
type est décrit plus en détail dans le §8.7 et la liaison dynamique dans le §12.3.1.
8.2.5 Type de chaîne
Le string
type est un type de classe scellé qui hérite directement de object
. Les instances de la string
classe représentent des chaînes de caractères Unicode.
Les valeurs du string
type peuvent être écrites en tant que littéraux de chaîne (§6.4.5.6).
Le mot clé string
est simplement un alias pour la classe System.String
prédéfinie.
Types d’interface 8.2.6
Une interface définit un contrat. Une classe ou un struct qui implémente une interface doit respecter son contrat. Une interface peut hériter de plusieurs interfaces de base, et une classe ou un struct peut implémenter plusieurs interfaces.
Les types d’interface sont décrits dans le §18.
8.2.7 Types de tableaux
Un tableau est une structure de données qui contient zéro ou plusieurs variables, accessibles via des index calculés. Les variables contenues dans un tableau, également appelé éléments du tableau, sont tous du même type, et ce type est appelé type d’élément du tableau.
Les types de tableaux sont décrits dans le §17.
8.2.8 Types délégués
Un délégué est une structure de données qui fait référence à une ou plusieurs méthodes. Par exemple, les méthodes font également référence à leurs instances d’objet correspondantes.
Remarque : L’équivalent le plus proche d’un délégué en C ou C++ est un pointeur de fonction, mais alors qu’un pointeur de fonction ne peut référencer que des fonctions statiques, un délégué peut référencer des méthodes statiques et d’instance. Dans ce dernier cas, le délégué stocke non seulement une référence au point d’entrée de la méthode, mais également une référence à l’instance d’objet sur laquelle appeler la méthode. Note de fin
Les types délégués sont décrits dans le §20.
8.3 Types valeur
8.3.1 Général
Un type valeur est un type de struct ou un type d’énumération. C# fournit un ensemble de types de struct prédéfinis appelés types simples. Les types simples sont identifiés par le biais de mots clés.
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
Contrairement à une variable d’un type référence, une variable d’un type valeur peut contenir la valeur null
uniquement si le type valeur est un type valeur nullable (§8.3.12). Pour chaque type de valeur non nullable, il existe un type valeur nullable correspondant indiquant le même jeu de valeurs plus la valeur null
.
L’affectation à une variable d’un type valeur crée une copie de la valeur affectée. Cela diffère de l’affectation à une variable d’un type référence, qui copie la référence, mais pas l’objet identifié par la référence.
8.3.2 Type System.ValueType
Tous les types valeur héritent implicitement du class
System.ValueType
, qui, à son tour, hérite de la classe object
. Il n’est pas possible pour un type de dériver d’un type valeur et les types valeur sont donc implicitement scellés (§15.2.2.3).
Notez que System.ValueType
ce n’est pas lui-même une value_type. Il s’agit plutôt d’une class_type à partir de laquelle tous les value_typesont automatiquement dérivés.
8.3.3 Constructeurs par défaut
Tous les types valeur déclarent implicitement un constructeur d’instance sans paramètre public appelé constructeur par défaut. Le constructeur par défaut retourne une instance initialisée zéro appelée valeur par défaut pour le type valeur :
- Pour toutes les simple_types, la valeur par défaut est la valeur produite par un modèle de bits de tous les zéros :
- Pour
sbyte
,byte
uint
short
int
long
ushort
etulong
, la valeur par défaut est .0
- Pour
char
, la valeur par défaut est'\x0000'
. - Pour
float
, la valeur par défaut est0.0f
. - Pour
double
, la valeur par défaut est0.0d
. - Pour
decimal
, la valeur par défaut est0m
(c’est-à-dire la valeur zéro avec l’échelle 0). - Pour
bool
, la valeur par défaut estfalse
. - Pour un enum_type
E
, la valeur par défaut est0
, convertie en typeE
.
- Pour
- Pour un struct_type, la valeur par défaut est la valeur produite en définissant tous les champs de type valeur sur leur valeur par défaut et tous les champs de type référence sur
null
. - Pour un nullable_value_type la valeur par défaut est une instance pour laquelle la
HasValue
propriété a la valeur false. La valeur par défaut est également appelée valeur Null du type valeur nullable. La tentative de lecture de laValue
propriété d’une telle valeur entraîne la levée d’une exception de typeSystem.InvalidOperationException
(§8.3.12).
Comme tout autre constructeur d’instance, le constructeur par défaut d’un type valeur est appelé à l’aide de l’opérateur new
.
Remarque : Pour des raisons d’efficacité, cette exigence n’est pas destinée à faire en sorte que l’implémentation génère un appel de constructeur. Pour les types valeur, l’expression de valeur par défaut (§12.8.21) produit le même résultat que l’utilisation du constructeur par défaut. Note de fin
Exemple : Dans le code ci-dessous, les variables
i
j
etk
sont toutes initialisées à zéro.class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
exemple de fin
Étant donné que chaque type valeur a implicitement un constructeur d’instance sans paramètre public, il n’est pas possible qu’un type de struct contienne une déclaration explicite d’un constructeur sans paramètre. Un type de struct est toutefois autorisé à déclarer des constructeurs d’instances paramétrables (§16.4.9).
Types de struct 8.3.4
Un type de struct est un type valeur qui peut déclarer des constantes, des champs, des méthodes, des propriétés, des événements, des indexeurs, des opérateurs, des constructeurs d’instance, des constructeurs statiques et des types imbriqués. La déclaration des types de structs est décrite dans le §16.
8.3.5 Types simples
C# fournit un ensemble de types prédéfinis struct
appelés types simples. Les types simples sont identifiés par le biais de mots clés, mais ces mots clés sont simplement des alias pour les types prédéfinis struct
dans l’espace System
de noms, comme décrit dans le tableau ci-dessous.
Mot clé | Type alias |
---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
Étant donné qu’un type simple alias est un type de struct, chaque type simple a des membres.
Exemple :
int
les membres déclarés etSystem.Int32
les membres hérités deSystem.Object
, et les instructions suivantes sont autorisées :int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
exemple de fin
Remarque : Les types simples diffèrent des autres types de structs dans montrant qu’ils autorisent certaines opérations supplémentaires :
- La plupart des types simples permettent de créer des valeurs en écrivant des littéraux (§6.4.5), bien que C# ne provisionne pas les littéraux des types de structs en général. Exemple :
123
est un littéral de typeint
et'a'
est un littéral de typechar
. exemple de fin- Lorsque les opérandes d’une expression sont toutes des constantes de type simple, il est possible que le compilateur évalue l’expression au moment de la compilation. Une telle expression est appelée constant_expression (§12.23). Les expressions impliquant des opérateurs définis par d’autres types de struct ne sont pas considérées comme des expressions constantes
- Par le biais
const
de déclarations, il est possible de déclarer des constantes des types simples (§15.4). Il n’est pas possible d’avoir des constantes d’autres types de structs, mais un effet similaire est fourni par des champs en lecture seule statiques.- Les conversions impliquant des types simples peuvent participer à l’évaluation des opérateurs de conversion définis par d’autres types de struct, mais un opérateur de conversion défini par l’utilisateur ne peut jamais participer à l’évaluation d’un autre opérateur de conversion défini par l’utilisateur (§10.5.3).
note de fin.
8.3.6 Types intégraux
C# prend en charge neuf types intégraux : sbyte
, int
uint
byte
short
ushort
, long
, ulong
, et .char
Les types intégraux ont les tailles et plages de valeurs suivantes :
- Le
sbyte
type représente des entiers 8 bits signés dont les valeurs sont comprises entre-128
127
et inclus. - Le
byte
type représente des entiers 8 bits non signés dont les valeurs sont comprises entre0
255
et inclus. - Le
short
type représente des entiers 16 bits signés avec des valeurs comprises entre-32768
32767
et inclus. - Le
ushort
type représente des entiers 16 bits non signés dont les valeurs sont comprises entre0
65535
et inclus. - Le
int
type représente des entiers 32 bits signés dont les valeurs sont comprises entre-2147483648
2147483647
et inclus. - Le
uint
type représente des entiers 32 bits non signés dont les valeurs sont comprises entre0
4294967295
et inclus. - Le
long
type représente des entiers 64 bits signés dont les valeurs sont comprises entre-9223372036854775808
9223372036854775807
et inclus. - Le
ulong
type représente des entiers 64 bits non signés dont les valeurs sont comprises entre0
18446744073709551615
et inclus. - Le
char
type représente des entiers 16 bits non signés dont les valeurs sont comprises entre0
65535
et inclus. L’ensemble de valeurs possibles pour lechar
type correspond au jeu de caractères Unicode.Remarque : Bien qu’elle
char
ait la même représentation queushort
, toutes les opérations autorisées sur un type ne sont pas autorisées sur l’autre. Note de fin
Tous les types intégraux signés sont représentés à l’aide du format de complément de deux.
Les integral_type opérateurs unaires et binaires fonctionnent toujours avec une précision 32 bits signée, une précision 32 bits non signée, une précision 64 bits signée ou une précision 64 bits non signée, comme détaillé dans le §12.4.7.
Le char
type est classé comme un type intégral, mais il diffère des autres types intégraux de deux façons :
- Il n’existe aucune conversion implicite prédéfinie d’autres types vers le
char
type. En particulier, même si les types et lesbyte
types ont des plages de valeurs qui sont entièrement représentées à l’aide duchar
type, des conversions implicites à partir d’octets, d’octets ouushort
d’existerchar
.ushort
- Les constantes du
char
type doivent être écrites en tant que character_literals ou en tant que integer_literals en combinaison avec un cast en char de type.
Exemple :
(char)10
est identique à'\x000A'
. exemple de fin
Les checked
opérateurs et unchecked
instructions sont utilisés pour contrôler la vérification de dépassement de capacité pour les opérations et conversions arithmétiques de type intégral (§12.8.20). Dans un checked
contexte, un dépassement de capacité génère une erreur au moment de la compilation ou provoque la levée d’une System.OverflowException
erreur. Dans un unchecked
contexte, les dépassements de capacité sont ignorés et tous les bits de commande élevé qui ne correspondent pas au type de destination sont ignorés.
8.3.7 Types à virgule flottante
C# prend en charge deux types à virgule flottante : float
et double
. Les float
types et double
les types sont représentés à l’aide des formats IEC 64 bits à double précision 60559 32 bits, qui fournissent les ensembles de valeurs suivants :
- Zéro positif et zéro négatif. Dans la plupart des cas, le zéro positif et le zéro négatif se comportent de la même façon que la valeur simple zéro, mais certaines opérations font la distinction entre les deux (§12.10.3).
- Infini positif et infini négatif. Les infinis sont produits par des opérations telles que la division d’un nombre différent de zéro par zéro.
Exemple :
1.0 / 0.0
produit l’infini positif et–1.0 / 0.0
génère l’infini négatif. exemple de fin - Valeur Not-a-Number , souvent abrégée NaN. Les valeurs NaN sont produites par des opérations à virgule flottante non valides, telles que la division de zéro par zéro.
- Ensemble fini de valeurs non nulles du formulaire s × m × 2e, où s est 1 ou −1, et m et e sont déterminés par le type à virgule flottante particulière : Pour
float
, 0 <m< 2²⁴ et −149 ≤ e ≤ 104, et pourdouble
, 0 <m< 2⁵³ et −1075 ≤ e ≤ 970. Les nombres à virgule flottante dénormalisée sont considérés comme des valeurs non nulles valides. C# n’exige ni interdit qu’une implémentation conforme prend en charge les nombres à virgule flottante dénormalisée.
Le float
type peut représenter des valeurs allant d’environ 1,5 × 10⁻⁴⁵ à 3,4 × 10³⁸ avec une précision de 7 chiffres.
Le double
type peut représenter des valeurs allant d’environ 5,0 × 10⁻³²⁴ à 1,7 × 10³⁰⁸ avec une précision de 15 à 16 chiffres.
Si l’un des opérandes d’un opérateur binaire est un type à virgule flottante, les promotions numériques standard sont appliquées, comme indiqué dans le §12.4.7, et l’opération est effectuée avec float
ou double
précision.
Les opérateurs à virgule flottante, y compris les opérateurs d’affectation, ne produisent jamais d’exceptions. Au lieu de cela, dans des situations exceptionnelles, les opérations à virgule flottante produisent zéro, infini ou NaN, comme décrit ci-dessous :
- Le résultat d’une opération à virgule flottante est arrondi à la valeur représentée la plus proche au format de destination.
- Si l’ampleur du résultat d’une opération à virgule flottante est trop petite pour le format de destination, le résultat de l’opération devient zéro positif ou zéro négatif.
- Si l’ampleur du résultat d’une opération à virgule flottante est trop grande pour le format de destination, le résultat de l’opération devient l’infini positif ou l’infini négatif.
- Si une opération à virgule flottante n’est pas valide, le résultat de l’opération devient NaN.
- Si l’un ou les deux opérandes d’une opération à virgule flottante sont NaN, le résultat de l’opération devient NaN.
Les opérations à virgule flottante peuvent être effectuées avec une précision supérieure au type de résultat de l’opération. Pour forcer une valeur d’un type à virgule flottante à la précision exacte de son type, un cast explicite (§12.9.7) peut être utilisé.
Exemple : Certaines architectures matérielles prennent en charge un type à virgule flottante « étendu » ou « long double » avec une plage et une précision supérieures au
double
type, et effectuent implicitement toutes les opérations à virgule flottante à l’aide de ce type de précision plus élevé. À un coût excessif de performances, ces architectures matérielles peuvent être effectuées pour effectuer des opérations à virgule flottante avec moins de précision, et plutôt que de nécessiter une implémentation pour perdre les performances et la précision, C# permet d’utiliser un type de précision plus élevé pour toutes les opérations à virgule flottante. En plus de fournir des résultats plus précis, cela a rarement des effets mesurables. Toutefois, dans les expressions de la formex * y / z
, où la multiplication produit un résultat qui se trouve en dehors de ladouble
plage, mais la division suivante ramène le résultat temporaire dans ladouble
plage, le fait que l’expression est évaluée dans un format de plage plus élevé peut entraîner la production d’un résultat fini au lieu d’une infinité. exemple de fin
8.3.8 Type décimal
Le type decimal
est un type de données 128 bits adapté aux calculs financiers et monétaires. Le decimal
type peut représenter des valeurs, y compris celles comprises dans la plage au moins -7,9 × 10⁻²⁸ à 7,9 × 10²⁸, avec au moins 28 chiffres précision.
L’ensemble fini de valeurs de type decimal
est de la forme (–1)v × c × 10⁻e, où le signe v est 0 ou 1, le coefficient c est donné par 0 ≤ cmax<, et l’échelle e est telle que Emin ≤ e ≤ Emax, où Cmax est au moins 1 × 10²⁸, Emin ≤ 0, et Emax ≥ 28. Le decimal
type ne prend pas nécessairement en charge les zéros signés, les infinis ou les NaN.
Un decimal
est représenté sous la forme d’un entier mis à l’échelle par une puissance de dix. Pour decimal
s avec une valeur absolue inférieure 1.0m
à , la valeur est exacte à au moins la 28e décimale. Pour decimal
s avec une valeur absolue supérieure ou égale à 1.0m
, la valeur est exacte à au moins 28 chiffres. Contrairement aux types de données et double
aux float
types de données, les nombres fractionnaires décimaux tels que 0.1
peuvent être représentés exactement dans la représentation décimale. Dans les représentations et double
les float
représentations, ces nombres ont souvent des expansions binaires sans fin, ce qui rend ces représentations plus sujettes à des erreurs d’arrondi.
Si l’un des opérandes d’un opérateur binaire est de decimal
type, les promotions numériques standard sont appliquées, comme indiqué dans le §12.4.7, et l’opération est effectuée avec double
précision.
Le résultat d’une opération sur les valeurs de type decimal
est celui qui résulterait du calcul d’un résultat exact (préservation de l’échelle, telle que définie pour chaque opérateur), puis arrondi pour s’adapter à la représentation. Les résultats sont arrondis à la valeur représentée la plus proche et, lorsqu’un résultat est tout aussi proche de deux valeurs représentées, à la valeur qui a un nombre pair dans la position du chiffre le moins significatif (c’est ce qu’on appelle « arrondi du banquier »). Autrement dit, les résultats sont exacts à au moins la 28e décimale. Notez que l’arrondi peut produire une valeur nulle à partir d’une valeur différente de zéro.
Si une decimal
opération arithmétique produit un résultat dont l’ampleur est trop grande pour le decimal
format, une System.OverflowException
opération est levée.
Le decimal
type a une plus grande précision, mais peut avoir une plage plus petite que les types à virgule flottante. Par conséquent, les conversions des types à virgule flottante pour decimal
produire des exceptions de dépassement de capacité, et les conversions des decimal
types à virgule flottante peuvent entraîner une perte de précision ou d’exceptions de dépassement de capacité. Pour ces raisons, aucune conversion implicite n’existe entre les types à virgule flottante et decimal
, sans casts explicites, une erreur au moment de la compilation se produit lorsque des opérandes et decimal
des virgules flottantes sont directement mélangés dans la même expression.
8.3.9 Type Bool
Le bool
type représente des quantités logiques booléennes. Les valeurs possibles du type bool
sont true
et false
. La représentation est false
décrite dans le §8.3.3. Bien que la représentation d’une true
représentation ne soit pas spécifiée, elle sera différente de celle de false
.
Aucune conversion standard n’existe entre bool
d’autres types valeur. En particulier, le bool
type est distinct et distinct des types intégraux, une bool
valeur ne peut pas être utilisée à la place d’une valeur intégrale, et vice versa.
Remarque : Dans les langages C et C++, une valeur intégrale ou à virgule flottante zéro, ou un pointeur null peut être converti en valeur
false
booléenne et une valeur intégrale ou à virgule flottante non nulle, ou un pointeur non null peut être converti en valeurtrue
booléenne. En C#, ces conversions sont effectuées en comparant explicitement une valeur intégrale ou à virgule flottante à zéro, ou en comparant explicitement une référence d’objet ànull
. Note de fin
8.3.10 Types d’énumération
Un type d’énumération est un type distinct avec des constantes nommées. Chaque type d’énumération a un type sous-jacent, qui doit être , , ushort
, , uint
int
, ou ulong
long
. short
sbyte
byte
L’ensemble de valeurs du type d’énumération est identique à l’ensemble de valeurs du type sous-jacent. Les valeurs du type d’énumération ne sont pas limitées aux valeurs des constantes nommées. Les types d’énumération sont définis par le biais de déclarations d’énumération (§19.2).
8.3.11 Types Tuple
Un type tuple représente une séquence ordonnée et de longueur fixe de valeurs avec des noms facultatifs et des types individuels. Le nombre d’éléments d’un type tuple est appelé arité. Un type tuple est écrit (T1 I1, ..., Tn In)
avec n ≥ 2, où les identificateurs sont des noms d’éléments tuple facultatifsI1...In
.
Cette syntaxe est abrégée pour un type construit avec les types T1...Tn
de System.ValueTuple<...>
, qui doit être un ensemble de types de struct génériques capables d’exprimer directement des types tuples de toute arité comprise entre deux et sept inclus.
Il n’est pas nécessaire d’exister une System.ValueTuple<...>
déclaration qui correspond directement à la arité d’un type tuple avec un nombre correspondant de paramètres de type. Au lieu de cela, les tuples avec une arité supérieure à sept sont représentés avec un type System.ValueTuple<T1, ..., T7, TRest>
de struct générique qui en plus des éléments tuple a un Rest
champ contenant une valeur imbriquée des éléments restants, à l’aide d’un autre System.ValueTuple<...>
type. Ces imbrications peuvent être observables de différentes façons, par exemple par le biais de la présence d’un Rest
champ. Lorsqu’un seul champ supplémentaire est requis, le type System.ValueTuple<T1>
de struct générique est utilisé ; ce type n’est pas considéré comme un type tuple en soi. Lorsque plus de sept champs supplémentaires sont requis, System.ValueTuple<T1, ..., T7, TRest>
il est utilisé de manière récursive.
Les noms d’éléments dans un type tuple doivent être distincts. Un nom d’élément tuple du formulaire ItemX
, où X
est n’importe quelle séquence de chiffres décimaux non0
initiés pouvant représenter la position d’un élément tuple, n’est autorisé qu’à la position indiquée par X
.
Les noms d’éléments facultatifs ne sont pas représentés dans les ValueTuple<...>
types et ne sont pas stockés dans la représentation runtime d’une valeur de tuple. Les conversions d’identités (§10.2.2) existent entre les tuples avec des séquences convertibles d’identité de types d’éléments.
L’opérateur new
§12.8.17.2 ne peut pas être appliqué avec la syntaxe new (T1, ..., Tn)
de type tuple. Les valeurs de tuple peuvent être créées à partir d’expressions tuple (§12.8.6) ou en appliquant directement l’opérateur new
à un type construit à partir de ValueTuple<...>
.
Les éléments tuple sont des champs publics avec les noms Item1
, Item2
etc., et sont accessibles via un accès membre sur une valeur de tuple (§12.8.7). En outre, si le type tuple a un nom pour un élément donné, ce nom peut être utilisé pour accéder à l’élément en question.
Remarque : Même si des tuples volumineux sont représentés avec des valeurs imbriquées
System.ValueTuple<...>
, chaque élément tuple est toujours accessible directement avec leItem...
nom correspondant à sa position. Note de fin
Exemple : Étant donné les exemples suivants :
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
Les types tuple pour
pair1
,pair2
etpair3
sont tous valides, avec des noms pour non, certains ou tous les éléments de type tuple.Le type
pair4
de tuple est valide, car les nomsItem1
etItem2
correspondent à leurs positions, tandis que le type tuple pourpair5
lequel il est interdit, car les nomsItem2
etItem123
ne le font pas.Les déclarations pour
pair6
etpair7
montrent que les types tuples sont interchangeables avec les types construits du formulaireValueTuple<...>
et que l’opérateurnew
est autorisé avec la dernière syntaxe.La dernière ligne indique que les éléments tuples sont accessibles par le
Item
nom correspondant à leur position, ainsi que par le nom de l’élément tuple correspondant, s’ils sont présents dans le type. exemple de fin
8.3.12 Types valeur Nullable
Un type valeur nullable peut représenter toutes les valeurs de son type sous-jacent, ainsi qu’une valeur null supplémentaire. Un type valeur nullable est écrit T?
, où T
est le type sous-jacent. Cette syntaxe est abrégée pour System.Nullable<T>
, et les deux formulaires peuvent être utilisés de manière interchangeable.
À l’inverse, un type valeur non nullable est n’importe quel type valeur autre que System.Nullable<T>
et son raccourci T?
(pour n’importe quel T
), ainsi que tout paramètre de type qui est contraint d’être un type valeur non nullable (autrement dit, tout paramètre de type avec une contrainte de type valeur (§15.2.5)). Le System.Nullable<T>
type spécifie la contrainte de type valeur pour T
, ce qui signifie que le type sous-jacent d’un type valeur nullable peut être n’importe quel type de valeur non nullable. Le type sous-jacent d’un type valeur nullable ne peut pas être un type valeur Nullable ou un type référence. Par exemple, int??
il s’agit d’un type non valide. Les types de référence nullables sont couverts dans le §8.9.
Une instance d’un type T?
valeur nullable a deux propriétés en lecture seule publiques :
- Propriété
HasValue
de typebool
- Propriété
Value
de typeT
Une instance pour laquelle HasValue
on dit qu’il s’agit true
d’une valeur non null. Une instance non null contient une valeur connue et Value
retourne cette valeur.
Instance pour laquelle HasValue
on dit qu’il s’agit false
de null. Une instance Null a une valeur non définie. La tentative de lecture de l’instance Value
Null provoque une System.InvalidOperationException
levée. Le processus d’accès à la propriété Value d’une instance nullable est appelé unwrapping.
En plus du constructeur par défaut, chaque type T?
valeur nullable a un constructeur public avec un seul paramètre de type T
. Compte tenu d’une valeur x
de type T
, appel d’un constructeur du formulaire
new T?(x)
crée une instance non null pour T?
laquelle la Value
propriété est x
. Le processus de création d’une instance non null d’un type valeur nullable pour une valeur donnée est appelé wrapping.
Les conversions implicites sont disponibles à partir du null
littéral vers T?
(§10.2.7) et de T
vers T?
(§10.2.6).
Le type T?
valeur nullable implémente aucune interface (§18). En particulier, cela signifie qu’il n’implémente aucune interface que le type T
sous-jacent effectue.
8.3.13 Boxing et unboxing
Le concept de boxe et d’unboxing fournit un pont entre les value_typeet les reference_types en autorisant toute valeur d’un value_type à convertir en et à partir du type object
. Boxing et unboxing permet une vue unifiée du système de type dans lequel une valeur de n’importe quel type peut finalement être traitée comme un object
.
La boxe est décrite plus en détail dans le §10.2.9 et unboxing est décrite dans le §10.3.7.
8.4 Types construits
8.4.1 Général
Une déclaration de type générique, par lui-même, désigne un type générique non lié utilisé comme « blueprint » pour former de nombreux types différents, en appliquant des arguments de type. Les arguments de type sont écrits entre crochets angle (<
et >
) immédiatement après le nom du type générique. Un type qui inclut au moins un argument de type est appelé type construit. Un type construit peut être utilisé dans la plupart des endroits dans la langue dans laquelle un nom de type peut apparaître. Un type générique indépendant ne peut être utilisé qu’au sein d’un typeof_expression (§12.8.18).
Les types construits peuvent également être utilisés dans des expressions comme noms simples (§12.8.4) ou lors de l’accès à un membre (§12.8.7).
Lorsqu’un namespace_or_type_name est évalué, seuls les types génériques avec le nombre correct de paramètres de type sont pris en compte. Par conséquent, il est possible d’utiliser le même identificateur pour identifier différents types, tant que les types ont différents nombres de paramètres de type. Cela est utile lors de la combinaison de classes génériques et non génériques dans le même programme.
Exemple :
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
exemple de fin
Les règles détaillées pour la recherche de noms dans les productions namespace_or_type_name sont décrites dans le §7.8. La résolution des ambiguïtés dans ces productions est décrite dans le §6.2.5. Un type_name peut identifier un type construit même s’il ne spécifie pas directement les paramètres de type. Cela peut se produire lorsqu’un type est imbriqué dans une déclaration générique class
et que le type d’instance de la déclaration conteneur est implicitement utilisé pour la recherche de noms (§15.3.9.7).
Exemple :
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
exemple de fin
Un type non construit ne doit pas être utilisé comme unmanaged_type (§8.8).
8.4.2 Arguments de type
Chaque argument d’une liste d’arguments de type est simplement un type.
type_argument_list
: '<' type_arguments '>'
;
type_arguments
: type_argument (',' type_argument)*
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Chaque argument de type doit satisfaire à toutes les contraintes sur le paramètre de type correspondant (§15.2.5). Argument de type référence dont la valeur nullabilité ne correspond pas à la valeur Nullabilité du paramètre de type satisfait à la contrainte ; toutefois, un avertissement peut être émis.
8.4.3 Types ouverts et fermés
Tous les types peuvent être classés comme types ouverts ou fermés. Un type ouvert est un type qui implique des paramètres de type. Plus précisément :
- Un paramètre de type définit un type ouvert.
- Un type de tableau est un type ouvert si et uniquement si son type d’élément est un type ouvert.
- Un type construit est un type ouvert si et seulement si un ou plusieurs de ses arguments de type sont un type ouvert. Un type imbriqué construit est un type ouvert si et seulement si un ou plusieurs de ses arguments de type ou les arguments de type de son ou de ses types de type sont un type ouvert.
Un type fermé est un type qui n’est pas un type ouvert.
Au moment de l’exécution, tout le code d’une déclaration de type générique est exécuté dans le contexte d’un type construit fermé qui a été créé en appliquant des arguments de type à la déclaration générique. Chaque paramètre de type dans le type générique est lié à un type d’exécution particulier. Le traitement au moment de l’exécution de toutes les instructions et expressions se produit toujours avec des types fermés, et les types ouverts se produisent uniquement pendant le traitement au moment de la compilation.
Deux types construits fermés sont convertibles d’identité (§10.2.2) s’ils sont construits à partir du même type générique indépendant et qu’une conversion d’identité existe entre chacun de leurs arguments de type correspondants. Les arguments de type correspondants peuvent eux-mêmes être des types construits ou des tuples construits qui sont convertibles d’identité. Les types construits fermés qui sont convertibles d’identité partagent un seul ensemble de variables statiques. Sinon, chaque type construit fermé a son propre ensemble de variables statiques. Étant donné qu’un type ouvert n’existe pas au moment de l’exécution, il n’existe aucune variable statique associée à un type ouvert.
8.4.4 Types liés et non liés
Le terme unbound type fait référence à un type non générique ou à un type générique non lié. Le type lié au terme fait référence à un type non générique ou à un type construit.
Un type indépendant fait référence à l’entité déclarée par une déclaration de type. Un type générique indépendant n’est pas lui-même un type et ne peut pas être utilisé comme type d’une variable, d’un argument ou d’une valeur de retour, ou comme type de base. La seule construction dans laquelle un type générique indépendant peut être référencé est l’expression typeof
(§12.8.18).
8.4.5 Contraintes satisfaisantes
Chaque fois qu’un type construit ou une méthode générique est référencé, les arguments de type fournis sont vérifiés par rapport aux contraintes de paramètre de type déclarées sur le type ou la méthode générique (§15.2.5). Pour chaque where
clause, l’argument A
de type qui correspond au paramètre de type nommé est vérifié par rapport à chaque contrainte comme suit :
- Si la contrainte est un
class
type, un type d’interface ou un paramètre de type, nous allonsC
représenter cette contrainte avec les arguments de type fournis substitués à tous les paramètres de type qui apparaissent dans la contrainte. Pour satisfaire la contrainte, il doit s’agir du cas où le typeA
est convertible en typeC
d’une des manières suivantes : - Si la contrainte est la contrainte de type référence (
class
), le typeA
doit satisfaire l’une des conditions suivantes :A
est un type d’interface, un type de classe, un type délégué, un type de tableau ou un type dynamique.
Remarque :
System.ValueType
etSystem.Enum
sont des types référence qui répondent à cette contrainte. Note de finA
est un paramètre de type connu pour être un type de référence (§8.2).
- Si la contrainte est la contrainte de type valeur (
struct
), le typeA
doit satisfaire l’une des conditions suivantes :A
est un type ouenum
unstruct
type, mais pas un type valeur nullable.
Remarque :
System.ValueType
etSystem.Enum
sont des types référence qui ne répondent pas à cette contrainte. Note de finA
est un paramètre de type ayant la contrainte de type valeur (§15.2.5).
- Si la contrainte est la contrainte
new()
du constructeur, le typeA
ne doit pas êtreabstract
et doit avoir un constructeur sans paramètre public. Ceci est satisfait si l’une des valeurs suivantes est vraie :A
est un type valeur, car tous les types valeur ont un constructeur public par défaut (§8.3.3).A
est un paramètre de type ayant la contrainte de constructeur (§15.2.5).A
est un paramètre de type ayant la contrainte de type valeur (§15.2.5).A
est unclass
qui n’est pas abstrait et contient un constructeur public explicitement déclaré sans paramètres.A
n’est pasabstract
et a un constructeur par défaut (§15.11.5).
Une erreur au moment de la compilation se produit si une ou plusieurs contraintes d’un paramètre de type ne sont pas satisfaites par les arguments de type donnés.
Étant donné que les paramètres de type ne sont pas hérités, les contraintes ne sont jamais héritées non plus.
Exemple : dans les éléments suivants,
D
doit spécifier la contrainte sur son paramètreT
de type afin deT
satisfaire la contrainte imposée par la baseclass
B<T>
. En revanche,class
E
il n’est pas nécessaire de spécifier une contrainte, carList<T>
implémenteIEnumerable
pour n’importe quelT
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
exemple de fin
8.5 Paramètres de type
Un paramètre de type est un identificateur désignant un type valeur ou un type référence auquel le paramètre est lié au moment de l’exécution.
type_parameter
: identifier
;
Étant donné qu’un paramètre de type peut être instancié avec de nombreux arguments de type différents, les paramètres de type ont des opérations et des restrictions légèrement différentes que d’autres types.
Remarque : Ces éléments sont les suivants :
- Un paramètre de type ne peut pas être utilisé directement pour déclarer une classe de base (§15.2.4.2) ou une interface (§18.2.4).
- Les règles de recherche de membre sur les paramètres de type dépendent des contraintes, le cas échéant, appliquées au paramètre de type. Elles sont détaillées dans le §12.5.
- Les conversions disponibles pour un paramètre de type dépendent des contraintes, le cas échéant, appliquées au paramètre de type. Elles sont détaillées dans le §10.2.12 et le §10.3.8.
- Le littéral
null
ne peut pas être converti en type donné par un paramètre de type, sauf si le paramètre de type est connu comme un type référence (§10.2.12). Toutefois, une expression par défaut (§12.8.21) peut être utilisée à la place. En outre, une valeur avec un type donné par un paramètre de type peut être comparée à null à l’aide==
et!=
(§12.12.7), sauf si le paramètre de type a la contrainte de type valeur.- Une
new
expression (§12.8.17.2) ne peut être utilisée qu’avec un paramètre de type si le paramètre de type est limité par un constructor_constraint ou la contrainte de type valeur (§15.2.5).- Un paramètre de type ne peut pas être utilisé n’importe où dans un attribut.
- Un paramètre de type ne peut pas être utilisé dans un accès membre (§12.8.7) ou un nom de type (§7.8) pour identifier un membre statique ou un type imbriqué.
- Un paramètre de type ne peut pas être utilisé comme unmanaged_type (§8.8).
Note de fin
En tant que type, les paramètres de type sont purement une construction au moment de la compilation. Au moment de l’exécution, chaque paramètre de type est lié à un type d’exécution spécifié en fournissant un argument de type à la déclaration de type générique. Par conséquent, le type d’une variable déclarée avec un paramètre de type sera, au moment de l’exécution, un type construit fermé §8.4.3. L’exécution de toutes les instructions et expressions impliquant des paramètres de type utilise le type fourni comme argument de type pour ce paramètre.
8.6 Types d’arborescences d’expressions
Les arborescences d’expressions permettent aux expressions lambda d’être représentées en tant que structures de données au lieu du code exécutable. Les arborescences d’expressions sont des valeurs des types d’arborescences d’expressions du formulaireSystem.Linq.Expressions.Expression<TDelegate>
, où TDelegate
est n’importe quel type délégué. Pour le reste de cette spécification, ces types seront référencés à l’aide du raccourci Expression<TDelegate>
.
Si une conversion existe d’une expression lambda en type D
délégué, une conversion existe également en type Expression<TDelegate>
d’arborescence d’expressions. Alors que la conversion d’une expression lambda en type délégué génère un délégué qui fait référence au code exécutable pour l’expression lambda, la conversion en type d’arborescence d’expressions crée une représentation d’arborescence d’expressions de l’expression lambda. Pour plus d’informations sur cette conversion, consultez le §10.7.3.
Exemple : le programme suivant représente une expression lambda en tant que code exécutable et en tant qu’arborescence d’expressions. Étant donné qu’une conversion existe
Func<int,int>
en , une conversion existe également enExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
À la suite de ces affectations, le délégué
del
fait référence à une méthode qui retournex + 1
, et l’exp de l’arborescence d’expressions fait référence à une structure de données qui décrit l’expressionx => x + 1
.exemple de fin
Expression<TDelegate>
fournit une méthode Compile
d’instance qui produit un délégué de type TDelegate
:
Func<int,int> del2 = exp.Compile();
L’appel de ce délégué entraîne l’exécution du code représenté par l’arborescence d’expressions. Ainsi, étant donné les définitions ci-dessus, del
et del2
sont équivalentes, et les deux instructions suivantes auront le même effet :
int i1 = del(1);
int i2 = del2(1);
Après l’exécution de ce code, i1
et i2
aura toutes les deux la valeur 2
.
L’aire d’API fournie par Expression<TDelegate>
l’implémentation est définie au-delà de l’exigence d’une Compile
méthode décrite ci-dessus.
Remarque : bien que les détails de l’API fournie pour les arborescences d’expressions soient définis par l’implémentation, il est prévu qu’une implémentation :
- Permettre au code d’inspecter et de répondre à la structure d’une arborescence d’expressions créée à la suite d’une conversion à partir d’une expression lambda
- Activer la création programmatique d’arborescences d’expressions dans le code utilisateur
Note de fin
8.7 Type dynamique
Le type dynamic
utilise la liaison dynamique, comme décrit en détail dans le §12.3.2, par opposition à la liaison statique utilisée par tous les autres types.
Le type dynamic
est considéré comme identique à l’exception object
des éléments suivants :
- Les opérations sur les expressions de type
dynamic
peuvent être liées dynamiquement (§12.3.3). - L’inférence de type (§12.6.3) préférera
dynamic
siobject
les deux sont candidats. dynamic
ne peut pas être utilisé comme- type dans un object_creation_expression (§12.8.17.2)
- un class_base (§15.2.4)
- un predefined_type dans un member_access (§12.8.7.1)
- opérande de l’opérateur
typeof
- un argument d’attribut
- une contrainte
- un type de méthode d’extension
- toute partie d’un argument de type dans struct_interfaces (§16.2.5) ou interface_type_list (§15.2.4.1).
En raison de cette équivalence, les éléments suivants sont les suivants :
- Il existe une conversion d’identité implicite
- entre
object
etdynamic
- entre les types construits identiques lors du remplacement par
dynamic
object
- entre les types tuples identiques lors du remplacement par
dynamic
object
- entre
- Les conversions implicites et explicites vers et
object
depuis s’appliquent également à et depuisdynamic
. - Les signatures identiques lors du
dynamic
object
remplacement sont considérées comme la même signature. - Le type
dynamic
est indistinguishable du typeobject
au moment de l’exécution. - Une expression du type
dynamic
est appelée expression dynamique.
8.8 Types non managés
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
Un unmanaged_type est n’importe quel type qui n’est pas un reference_type, un type_parameter ou un type construit, et ne contient aucun champ d’instance dont le type n’est pas un unmanaged_type. En d’autres termes, une unmanaged_type est l’une des suivantes :
sbyte
, ,byte
, , .uint
bool
short
ushort
int
long
ulong
char
float
double
decimal
- N’importe quelle enum_type.
- Tout struct_type défini par l’utilisateur qui n’est pas un type construit et contient uniquement des champs d’instance de unmanaged_types.
- Dans le code non sécurisé (§23.2), tout pointer_type (§23.3).
8.9 Types de référence et nullabilité
8.9.1 Général
Un type de référence nullable est indiqué en ajoutant un nullable_type_annotation (?
) à un type de référence non nullable. Il n’existe aucune différence sémantique entre un type référence non nullable et son type nullable correspondant, les deux peuvent être une référence à un objet ou null
. La présence ou l’absence du nullable_type_annotation déclare si une expression est destinée à autoriser ou non des valeurs Null. Un compilateur peut fournir des diagnostics lorsqu’une expression n’est pas utilisée selon cette intention. L’état null d’une expression est défini dans le §8.9.5. Une conversion d’identité existe entre un type référence nullable et son type de référence non Nullable correspondant (§10.2.2).
Il existe deux formes de nullabilité pour les types de référence :
- Nullable : un type nullable-reference peut être affecté
null
. Son état null par défaut est peut-être null. - non nullable : une référence non nullable ne doit pas être affectée à une
null
valeur. Son état null par défaut n’est pas null.
Remarque : les types
R
etR?
sont représentés par le même type sous-jacent.R
Une variable de ce type sous-jacent peut contenir une référence à un objet ou être la valeurnull
, qui indique « aucune référence ». Note de fin
La distinction syntactique entre un type de référence nullable et son type de référence non nullable correspondant permet à un compilateur de générer des diagnostics. Un compilateur doit autoriser le nullable_type_annotation tel que défini dans le §8.2.1. Les diagnostics doivent être limités aux avertissements. Ni la présence ni l’absence d’annotations nullables, ni l’état du contexte nullable ne peuvent modifier l’heure de compilation ou le comportement d’exécution d’un programme, à l’exception des modifications apportées aux messages de diagnostic générés au moment de la compilation.
8.9.2 Types de référence non nullables
Un type référence non nullable est un type référence du formulaire T
, où T
est le nom du type. L’état null par défaut d’une variable non nullable n’est pas Null. Les avertissements peuvent être générés lorsqu’une expression qui est peut-être null est utilisée lorsqu’une valeur non null est requise.
8.9.3 Types de référence Nullable
Un type de référence du formulaire T?
(par exemple string?
) est un type de référence nullable. L’état null par défaut d’une variable nullable est peut-être null. L’annotation ?
indique l’intention que les variables de ce type sont nullables. Le compilateur peut reconnaître ces intentions pour émettre des avertissements. Lorsque le contexte d’annotation nullable est désactivé, l’utilisation de cette annotation peut générer un avertissement.
8.9.4 Contexte nullable
8.9.4.1 Général
Chaque ligne de code source a un contexte nullable. Les annotations et les indicateurs d’avertissement pour les annotations nullables du contrôle de contexte nullable (§8.9.4.3) et les avertissements nullables (§8.9.4.4), respectivement. Chaque indicateur peut être activé ou désactivé. Le compilateur peut utiliser l’analyse de flux statique pour déterminer l’état Null de n’importe quelle variable de référence. L’état null d’une variable de référence (§8.9.5) n’est pas null, peut-être null ou peut-être par défaut.
Le contexte nullable peut être spécifié dans le code source via des directives nullables (§6.5.9) et/ou via un mécanisme spécifique à l’implémentation externe au code source. Si les deux approches sont utilisées, les directives nullables remplacent les paramètres effectués via un mécanisme externe.
L’état par défaut du contexte nullable est défini par l’implémentation.
Tout au long de cette spécification, tout le code C# qui ne contient pas de directives nullables ou dont aucune instruction n’est faite concernant l’état de contexte nullable actuel, est supposé avoir été compilé à l’aide d’un contexte nullable où les annotations et les avertissements sont activés.
Remarque : Contexte nullable où les deux indicateurs sont désactivés correspond au comportement standard précédent pour les types de référence. Note de fin
8.9.4.2 Désactivation nullable
Lorsque les indicateurs d’avertissement et d’annotations sont désactivés, le contexte nullable est désactivé.
Lorsque le contexte nullable est désactivé :
- Aucun avertissement ne doit être généré lorsqu’une variable d’un type de référence non annoté est initialisée avec, ou affectée à une valeur de .
null
- Aucun avertissement ne doit être généré lorsqu’une variable d’un type référence qui a éventuellement la valeur Null.
- Pour tout type
T
de référence, l’annotation?
dansT?
génère un message et le typeT?
est identique àT
. - Pour toute contrainte
where T : C?
de paramètre de type, l’annotation?
dansC?
génère un message et le typeC?
est identique àC
. - Pour toute contrainte
where T : U?
de paramètre de type, l’annotation?
dansU?
génère un message et le typeU?
est identique àU
. - La contrainte
class?
générique génère un message d’avertissement. Le paramètre de type doit être un type de référence.Remarque : Ce message est caractérisé comme « informationnel » plutôt que « avertissement », afin de ne pas le confondre avec l’état du paramètre d’avertissement nullable, qui n’est pas lié. Note de fin
- L’opérateur
!
null-forgiving (§12.8.9) n’a aucun effet.
Exemple :
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
exemple de fin
8.9.4.3 Annotations nullables
Lorsque l’indicateur d’avertissement est désactivé et que l’indicateur d’annotations est activé, le contexte nullable est des annotations.
Lorsque le contexte nullable est des annotations :
- Pour tout type
T
de référence, l’annotation?
inT?
indique qu’unT?
type nullable, tandis que l’non-nullableT
est non nullable. - Aucun avertissement de diagnostic lié à la nullabilité n’est généré.
- L’opérateur
!
null-forgiving (§12.8.9) peut modifier l’état null analysé de son opérande et les avertissements de diagnostic de compilation générés.
Exemple :
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
exemple de fin
8.9.4.4 Avertissements Nullables
Lorsque l’indicateur d’avertissement est activé et que l’indicateur d’annotations est désactivé, le contexte nullable est des avertissements.
Lorsque le contexte nullable est des avertissements, un compilateur peut générer des diagnostics dans les cas suivants :
- Une variable de référence qui a été déterminée comme étant peut-être null, est déréférencée.
- Une variable de référence d’un type non nullable est affectée à une expression qui est peut-être null.
- Il
?
est utilisé pour noter un type de référence nullable. - L’opérateur
!
null-forgiving (§12.8.9) est utilisé pour définir l’état Null de son opérande sur non null.
Exemple :
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
exemple de fin
8.9.4.5 Activation nullable
Lorsque l’indicateur d’avertissement et l’indicateur d’annotations sont activés, le contexte nullable est activé.
Lorsque le contexte nullable est activé :
- Pour n’importe quel type
T
de référence, l’annotation?
dansT?
faitT?
un type Nullable, tandis que l’non-nullableT
est non nullable. - Le compilateur peut utiliser l’analyse de flux statique pour déterminer l’état Null de n’importe quelle variable de référence. Lorsque des avertissements nullables sont activés, l’état null d’une variable de référence (§8.9.5) n’est pas null, peut-être null, ou peut-être par défaut et
- L’opérateur
!
null-forgiving (§12.8.9) définit l’état null de son opérande sur null. - Le compilateur peut émettre un avertissement si la valeur Nullabilité d’un paramètre de type ne correspond pas à la valeur Nullabilité de son argument de type correspondant.
8.9.5 Valeurs null et états Null
Un compilateur n’est pas nécessaire pour effectuer une analyse statique, ni pour générer des avertissements de diagnostic liés à la nullabilité.
Le reste de ce sous-projet est conditionnel.
Un compilateur qui génère des avertissements de diagnostic est conforme à ces règles.
Chaque expression a l’un des trois étatsnull :
- peut-être null : la valeur de l’expression peut avoir la valeur Null.
- peut-être par défaut : la valeur de l’expression peut évaluer la valeur par défaut pour ce type.
- non null : la valeur de l’expression n’est pas null.
L’état null par défaut d’une expression est déterminé par son type et l’état de l’indicateur d’annotations lorsqu’il est déclaré :
- L’état null par défaut d’un type référence nullable est :
- Peut-être null quand sa déclaration est en texte où l’indicateur d’annotations est activé.
- Non null lorsque sa déclaration est en texte où l’indicateur d’annotations est désactivé.
- L’état null par défaut d’un type référence non nullable n’est pas Null.
Remarque : L’état par défaut peut être utilisé avec des paramètres de type non contraints lorsque le type est un type non nullable, tel que
string
et que l’expressiondefault(T)
est la valeur Null. Étant donné que null n’est pas dans le domaine pour le type non nullable, l’état est peut-être par défaut. Note de fin
Un diagnostic peut être généré lorsqu’une variable (§9.2.1) d’un type de référence non nullable est initialisée ou affectée à une expression qui est peut-être null quand cette variable est déclarée dans le texte où l’indicateur d’annotation est activé.
Exemple : considérez la méthode suivante où un paramètre est nullable et que cette valeur est affectée à un type non nullable :
#nullable enable public class C { public void M(string? p) { // Assignment of maybe null value to non-nullable variable string s = p; } }
Le compilateur peut émettre un avertissement où le paramètre susceptible d’être null est affecté à une variable qui ne doit pas être null. Si le paramètre est vérifié par null avant l’affectation, le compilateur peut l’utiliser dans son analyse d’état nullable et ne pas émettre d’avertissement :
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // Use s } } }
exemple de fin
Le compilateur peut mettre à jour l’état Null d’une variable dans le cadre de son analyse.
Exemple : le compilateur peut choisir de mettre à jour l’état en fonction des instructions de votre programme :
#nullable enable public void M(string? p) { // p is maybe-null int length = p.Length; // p is not null. string s = p; if (s != null) { int l2 = s.Length; } // s is maybe null int l3 = s.Length; }
Dans l’exemple précédent, le compilateur peut décider qu’après l’instruction
int length = p.Length;
, l’état null dep
n’est pas null. S’il s’agissait de null, cette instruction aurait levée unNullReferenceException
. Cela est similaire au comportement si le code avait été précédé d’uneif (p == null) throw NullReferenceException();
exception, sauf que le code tel qu’écrit peut produire un avertissement, dont l’objectif est d’avertir qu’une exception peut être levée implicitement.
Plus loin dans la méthode, le code vérifie qu’il s
ne s’agit pas d’une référence Null. L’état null de s
peut être null après la fermeture du bloc activé null. Le compilateur peut déduire qu’il s’agit s
peut-être de null, car le code a été écrit pour supposer qu’il a peut-être été null. En règle générale, lorsque le code contient une vérification null, le compilateur peut déduire que la valeur a peut-être été null.exemple de fin
Exemple : le compilateur peut traiter une propriété (§15.7) comme une variable avec un état ou comme des accesseurs get and set indépendants (§15.7.3). En d’autres termes, un compilateur peut choisir si l’écriture dans une propriété modifie l’état Null de la lecture de la propriété, ou si la lecture d’une propriété modifie l’état Null de cette propriété.
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; } } }
Dans l’exemple précédent, le champ de stockage du champ
DisappearingProperty
est défini sur Null lorsqu’il est lu. Toutefois, un compilateur peut supposer que la lecture d’une propriété ne modifie pas l’état Null de cette expression. exemple de fin
Fin du texte normatif conditionnel
ECMA C# draft specification