13 Instructions
13.1 Général
C# fournit une variété d’instructions.
Remarque : la plupart de ces instructions seront familières aux développeurs qui ont programmé en C et C++. Note de fin
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement (§23.2) et fixed_statement (§23.7) sont disponibles uniquement dans le code non sécurisé (§23).
La embedded_statement non déterministe est utilisée pour les instructions qui apparaissent dans d’autres instructions. L’utilisation de embedded_statement plutôt que l’instruction exclut l’utilisation d’instructions de déclaration et d’instructions étiquetées dans ces contextes.
Exemple : le code
void F(bool b) { if (b) int i = 44; }
entraîne une erreur au moment de la compilation, car une
if
instruction nécessite une embedded_statement plutôt qu’une instruction pour saif
branche. Si ce code était autorisé, la variablei
serait déclarée, mais elle ne pouvait jamais être utilisée. Notez toutefois que, en plaçanti
la déclaration de 's dans un bloc, l’exemple est valide.exemple de fin
13.2 Points de terminaison et accessibilité
Chaque instruction a un point de terminaison. En termes intuitifs, le point de terminaison d’une instruction est l’emplacement qui suit immédiatement l’instruction. Les règles d’exécution pour les instructions composites (instructions qui contiennent des instructions incorporées) spécifient l’action qui est effectuée lorsque le contrôle atteint le point de terminaison d’une instruction incorporée.
Exemple : Lorsque le contrôle atteint le point de terminaison d’une instruction dans un bloc, le contrôle est transféré à l’instruction suivante dans le bloc. exemple de fin
Si une instruction peut être atteinte par exécution, l’instruction est dite accessible. À l’inverse, s’il n’y a aucune possibilité qu’une instruction soit exécutée, l’instruction est dite inaccessible.
Exemple : dans le code suivant
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }
le deuxième appel de Console.WriteLine est inaccessible, car il n’existe aucune possibilité que l’instruction soit exécutée.
exemple de fin
Un avertissement est signalé si une instruction autre que throw_statement, bloquer ou empty_statement est inaccessible. Il ne s’agit pas spécifiquement d’une erreur pour qu’une instruction soit inaccessible.
Remarque: pour déterminer si une instruction ou un point de terminaison particulier est accessible, un compilateur effectue une analyse de flux en fonction des règles d’accessibilité définies pour chaque instruction. L’analyse de flux prend en compte les valeurs des expressions constantes (§12.23) qui contrôlent le comportement des instructions, mais les valeurs possibles d’expressions non constantes ne sont pas prises en compte. En d’autres termes, à des fins d’analyse de flux de contrôle, une expression non constante d’un type donné est considérée comme ayant une valeur possible de ce type.
Dans l’exemple
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }
l’expression booléenne de l’instruction
if
est une expression constante, car les deux opérandes de l’opérateur==
sont des constantes. À mesure que l’expression constante est évaluée au moment de la compilation, la production de la valeurfalse
, l’appelConsole.WriteLine
est considéré comme inaccessible. Toutefois, sii
elle est modifiée pour être une variable localevoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
l’appel
Console.WriteLine
est considéré comme accessible, même si, en réalité, il ne sera jamais exécuté.Note de fin
Le bloc d’un membre de fonction ou d’une fonction anonyme est toujours considéré comme accessible. En évaluant successivement les règles d’accessibilité de chaque instruction dans un bloc, l’accessibilité d’une instruction donnée peut être déterminée.
Exemple : dans le code suivant
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }
l’accessibilité du second
Console.WriteLine
est déterminée comme suit :
- La première
Console.WriteLine
instruction d’expression est accessible, car le bloc de laF
méthode est accessible (§13.3).- Le point de terminaison de la première
Console.WriteLine
instruction d’expression est accessible, car cette instruction est accessible (§13.7 et §13.3).- L’instruction
if
est accessible, car le point de terminaison de la premièreConsole.WriteLine
instruction d’expression est accessible (§13.7 et §13.3).- La deuxième
Console.WriteLine
instruction d’expression est accessible, car l’expression booléenne de l’instructionif
n’a pas la valeurfalse
constante.exemple de fin
Il existe deux situations dans lesquelles il s’agit d’une erreur au moment de la compilation pour que le point de fin d’une instruction soit accessible :
Étant donné que l’instruction
switch
n’autorise pas une section switch à « passer » à la section switch suivante, il s’agit d’une erreur au moment de la compilation pour que le point de fin de la liste d’instructions d’une section switch soit accessible. Si cette erreur se produit, il s’agit généralement d’une indication qu’unebreak
instruction est manquante.Il s’agit d’une erreur au moment de la compilation pour le point de terminaison du bloc d’un membre de fonction ou d’une fonction anonyme qui calcule une valeur à atteindre. Si cette erreur se produit, il s’agit généralement d’une indication qu’une
return
instruction est manquante (§13.10.5).
13.3 Blocs
13.3.1 Général
Un bloc autorise l’écriture de plusieurs instructions dans les contextes où une seule instruction est autorisée.
block
: '{' statement_list? '}'
;
Un bloc se compose d’un statement_list facultatif (§13.3.2), placé entre accolades. Si la liste d’instructions est omise, le bloc est dit vide.
Un bloc peut contenir des instructions de déclaration (§13.6). L’étendue d’une variable locale ou d’une constante déclarée dans un bloc est le bloc.
Un bloc est exécuté comme suit :
- Si le bloc est vide, le contrôle est transféré au point de terminaison du bloc.
- Si le bloc n’est pas vide, le contrôle est transféré vers la liste des instructions. Quand et si le contrôle atteint le point de terminaison de la liste d’instructions, le contrôle est transféré au point de terminaison du bloc.
La liste d’instructions d’un bloc est accessible si le bloc lui-même est accessible.
Le point de terminaison d’un bloc est accessible si le bloc est vide ou si le point de terminaison de la liste d’instructions est accessible.
Un bloc qui contient une ou plusieurs yield
instructions (§13.15) est appelé bloc d’itérateur. Les blocs d’itérateur sont utilisés pour implémenter des membres de fonction en tant qu’itérateurs (§15.14). Certaines restrictions supplémentaires s’appliquent aux blocs d’itérateur :
- Il s’agit d’une erreur au moment de la compilation pour qu’une
return
instruction apparaisse dans un bloc d’itérateur (maisyield return
les instructions sont autorisées). - Il s’agit d’une erreur au moment de la compilation pour qu’un bloc itérateur contienne un contexte non sécurisé (§23.2). Un bloc d’itérateur définit toujours un contexte sécurisé, même quand sa déclaration est imbriquée dans un contexte non sécurisé.
13.3.2 Listes d’instructions
Une liste d’instructions se compose d’une ou plusieurs instructions écrites dans une séquence. Les listes d’instructions se produisent dans les blocs (§13.3) et dans les switch_block(§13.8.3).
statement_list
: statement+
;
Une liste d’instructions est exécutée en transférant le contrôle vers la première instruction. Quand et si le contrôle atteint le point de terminaison d’une instruction, le contrôle est transféré à l’instruction suivante. Quand et si le contrôle atteint le point de fin de la dernière instruction, le contrôle est transféré vers le point de terminaison de la liste d’instructions.
Une instruction dans une liste d’instructions est accessible si au moins l’une des valeurs suivantes est vraie :
- L’instruction est la première instruction et la liste d’instructions elle-même est accessible.
- Le point de terminaison de l’instruction précédente est accessible.
- L’instruction est une instruction étiquetée et l’étiquette est référencée par une instruction accessible
goto
.
Le point de terminaison d’une liste d’instructions est accessible si le point de terminaison de la dernière instruction de la liste est accessible.
13.4 Instruction vide
Une empty_statement ne fait rien.
empty_statement
: ';'
;
Une instruction vide est utilisée lorsqu’aucune opération n’est effectuée dans un contexte où une instruction est requise.
L’exécution d’une instruction vide transfère simplement le contrôle au point de terminaison de l’instruction. Ainsi, le point de terminaison d’une instruction vide est accessible si l’instruction vide est accessible.
Exemple : Une instruction vide peut être utilisée lors de l’écriture d’une
while
instruction avec un corps null :bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }
En outre, une instruction vide peut être utilisée pour déclarer une étiquette juste avant le «
}
» fermant d’un bloc :void F(bool done) { ... if (done) { goto exit; } ... exit: ; }
exemple de fin
13.5 Instructions étiquetées
Une labeled_statement permet à une instruction d’être précédée d’une étiquette. Les instructions étiquetées sont autorisées dans des blocs, mais elles ne sont pas autorisées en tant qu’instructions incorporées.
labeled_statement
: identifier ':' statement
;
Une instruction étiquetée déclare une étiquette portant le nom donné par l’identificateur. L’étendue d’une étiquette est le bloc entier dans lequel l’étiquette est déclarée, y compris les blocs imbriqués. Il s’agit d’une erreur au moment de la compilation pour deux étiquettes portant le même nom pour avoir des étendues qui se chevauchent.
Une étiquette peut être référencée à partir d’instructions goto
(§13.10.4) dans l’étendue de l’étiquette.
Remarque : Cela signifie que
goto
les instructions peuvent transférer le contrôle dans des blocs et hors blocs, mais jamais dans des blocs. Note de fin
Les étiquettes ont leur propre espace de déclaration et n’interfèrent pas avec d’autres identificateurs.
Exemple : l’exemple
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }
est valide et utilise le nom x comme paramètre et une étiquette.
exemple de fin
L’exécution d’une instruction étiquetée correspond exactement à l’exécution de l’instruction suivant l’étiquette.
En plus de l’accessibilité fournie par le flux normal de contrôle, une instruction étiquetée est accessible si l’étiquette est référencée par une instruction accessible goto
, sauf si l’instruction goto
se trouve à l’intérieur du try
bloc ou un bloc d’un catch
try_statement qui inclut un finally
bloc dont le point de terminaison est inaccessible et que l’instruction étiquetée se trouve en dehors du try_statement.
13.6 Instructions de déclaration
13.6.1 Général
Une declaration_statement déclare une ou plusieurs variables locales, une ou plusieurs constantes locales ou une fonction locale. Les instructions de déclaration sont autorisées dans les blocs et les blocs de commutateur, mais elles ne sont pas autorisées en tant qu’instructions incorporées.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Une variable locale est déclarée à l’aide d’un local_variable_declaration (§13.6.2). Une constante locale est déclarée à l’aide d’un local_constant_declaration (§13.6.3). Une fonction locale est déclarée à l’aide d’un local_function_declaration (§13.6.4).
Les noms déclarés sont introduits dans l’espace de déclaration englobant le plus proche (§7.3).
13.6.2 Déclarations de variables locales
13.6.2.1 Général
Une local_variable_declaration déclare une ou plusieurs variables locales.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Les déclarations implicitement typées contiennent le mot clé contextuel (§6.4.4) var
entraînant une ambiguïté syntaxique entre les trois catégories résolues comme suit :
- S’il n’existe aucun type nommé
var
dans l’étendue et que l’entrée correspond implicitly_typed_local_variable_declaration il est choisi ; - Sinon, si un type nommé
var
est dans l’étendue, implicitly_typed_local_variable_declaration n’est pas considéré comme une correspondance possible.
Dans une local_variable_declaration chaque variable est introduite par un déclarateur, qui est l’un des implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator ou ref_local_variable_declarator pour les variables locales implicitement typées, explicitement typées et ref. Le déclarateur définit le nom (identificateur) et la valeur initiale, le cas échéant, de la variable introduite.
S’il existe plusieurs déclarateurs dans une déclaration, ils sont traités, y compris les expressions d’initialisation, afin de gauche à droite (§9.4.4.5).
Remarque : Pour une local_variable_declaration ne se produisant pas en tant que for_initializer (§13.9.4) ou resource_acquisition (§13.14), cet ordre de gauche à droite équivaut à chaque déclarateur dans un local_variable_declaration distinct. Par exemple :
void F() { int x = 1, y, z = x * 2; }
équivaut à :
void F() { int x = 1; int y; int z = x * 2; }
Note de fin
La valeur d’une variable locale est obtenue dans une expression à l’aide d’un simple_name (§12.8.4). Une variable locale doit être définitivement affectée (§9.4) à chaque emplacement où sa valeur est obtenue. Chaque variable locale introduite par un local_variable_declaration est initialement non attribuée (§9.4.3). Si un déclarateur a une expression d’initialisation, la variable locale introduite est classée comme affectée à la fin du déclarateur (§9.4.4.5).
L’étendue d’une variable locale introduite par un local_variable_declaration est définie comme suit (§7.7) :
- Si la déclaration se produit en tant que for_initializer l’étendue est la for_initializer, for_condition, for_iterator et embedded_statement (§13.9.4) ;
- Si la déclaration se produit en tant que resource_acquisition , l’étendue est le bloc le plus externe de l’expansion sémantiquement équivalente du using_statement (§13.14) ;
- Sinon, l’étendue est le bloc dans lequel la déclaration se produit.
Il s’agit d’une erreur pour faire référence à une variable locale par nom dans une position textuelle qui précède son déclarateur, ou dans une expression d’initialisation dans son déclarateur. Dans l’étendue d’une variable locale, il s’agit d’une erreur au moment de la compilation pour déclarer une autre variable locale, une fonction locale ou une constante portant le même nom.
Le contexte ref-safe (§9.7.2) d’une variable locale ref est le contexte ref-safe de son initialisation variable_reference. Le contexte ref-safe des variables locales non ref est le bloc de déclaration.
13.6.2.2 Déclarations de variables locales implicitement typées
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Une implicitly_typed_local_variable_declaration introduit une variable locale unique, identificateur. L’expression ou variable_reference doit avoir un type au moment de la compilation. T
La première alternative déclare une variable avec une valeur initiale d’expression ; son type est T?
lorsqu’il T
s’agit d’un type référence non nullable, sinon son type est T
. La deuxième alternative déclare une variable ref avec une valeur initiale de ref
variable_reference ; son type est lorsqu’il ref T?
s’agit T
d’un type référence non nullable, sinon son type est ref T
. (ref_kind est décrit dans le §15.6.1.)
Exemple :
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;
Les déclarations de variable locale implicitement typées ci-dessus sont exactement équivalentes aux déclarations explicitement typées suivantes :
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;
Voici des déclarations de variables locales implicitement typées :
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itself
exemple de fin
13.6.2.3 Déclarations de variables locales typées explicitement
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
Une explicity_typed_local_variable_declaration introduit une ou plusieurs variables locales avec le type spécifié.
Si un local_variable_initializer est présent, son type doit être approprié en fonction des règles d’affectation simple (§12.21.2) ou d’initialisation de tableau (§17.7) et sa valeur est affectée comme valeur initiale de la variable.
13.6.2.4 Déclarations de variables locales ref typées explicitement
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
L’initialisation variable_reference doit avoir le type de typeet répondre aux mêmes exigences que pour une affectation de référence (§12.21.3).
Si ref_kind est ref readonly
, les identificateurs déclarés sont des références à des variables traitées en lecture seule. Sinon, si ref_kind est ref
, les identificateurs déclarés sont des références à des variables qui doivent être accessibles en écriture.
Il s’agit d’une erreur au moment de la compilation pour déclarer une variable locale ref, ou une variable d’un ref struct
type, dans une méthode déclarée avec le method_modifierasync
, ou dans un itérateur (§15.14).
13.6.3 Déclarations de constante locale
Une local_constant_declaration déclare une ou plusieurs constantes locales.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Le type d’un local_constant_declaration spécifie le type des constantes introduites par la déclaration. Le type est suivi d’une liste de constant_declarators, chacune d’entre elles introduit une nouvelle constante. Un constant_declarator se compose d’un identificateur qui nomme la constante, suivi d’un jeton «=
», suivi d’un constant_expression (§12.23) qui donne la valeur de la constante.
Le type et constant_expression d’une déclaration de constante locale doivent respecter les mêmes règles que celles d’une déclaration de membre constante (§15.4).
La valeur d’une constante locale est obtenue dans une expression à l’aide d’un simple_name (§12.8.4).
L’étendue d’une constante locale est le bloc dans lequel la déclaration se produit. Il s’agit d’une erreur de référence à une constante locale dans une position textuelle qui précède la fin de son constant_declarator.
Une déclaration de constante locale qui déclare plusieurs constantes équivaut à plusieurs déclarations de constantes uniques avec le même type.
13.6.4 Déclarations de fonction locale
Une local_function_declaration déclare une fonction locale.
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
Note de grammaire : lorsque vous reconnaissez un local_function_body si les alternatives d’null_conditional_invocation_expression et d’expression sont applicables, l’ancien doit être choisi. (§15.6.1)
Exemple : il existe deux cas d’usage courants pour les fonctions locales : les méthodes d’itérateur et les méthodes asynchrones. Dans les méthodes iterator, toute exception est observée uniquement lors de l’appel de code qui énumère la séquence retournée. Dans les méthodes asynchrones, toutes les exceptions sont observées uniquement lorsque la tâche retournée est attendue. L’exemple suivant illustre la séparation entre la validation de paramètres et l’implémentation de l’itérateur à l’aide d’une fonction locale :
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }
exemple de fin
Sauf indication contraire ci-dessous, la sémantique de tous les éléments de grammaire est la même que pour method_declaration (§15.6.1), lue dans le contexte d’une fonction locale au lieu d’une méthode.
L’identificateur d’une local_function_declaration doit être unique dans sa portée de bloc déclarée, y compris tout espace de déclaration de variable locale englobant. L’une des conséquences est que les local_function_declarationsurchargés ne sont pas autorisés.
Un local_function_declaration peut inclure un async
modificateur (§15.15) et un unsafe
modificateur (§23.1). Si la déclaration inclut le async
modificateur, le type de retour doit être void
ou un «TaskType»
type (§15.15.1). Si la déclaration inclut le static
modificateur, la fonction est une fonction locale statique ; sinon, il s’agit d’une fonction locale non statique. Il s’agit d’une erreur au moment de la compilation pour type_parameter_list ou parameter_list de contenir des attributs. Si la fonction locale est déclarée dans un contexte non sécurisé (§23.2), la fonction locale peut inclure du code non sécurisé, même si la déclaration de fonction locale n’inclut pas le unsafe
modificateur.
Une fonction locale est déclarée au niveau de l’étendue de bloc. Une fonction locale non statique peut capturer des variables à partir de l’étendue englobante alors qu’une fonction locale statique ne doit pas (elle n’a donc pas accès aux variables locales, paramètres, fonctions locales non statiques ou this
). Il s’agit d’une erreur au moment de la compilation si une variable capturée est lue par le corps d’une fonction locale non statique, mais n’est pas définitivement affectée avant chaque appel à la fonction. Un compilateur détermine quelles variables sont définitivement affectées au retour (§9.4.4.33).
Lorsque le type d’un this
type de struct est un type de struct, il s’agit d’une erreur au moment de la compilation pour le corps d’une fonction locale à accéder this
. Cela est vrai si l’accès est explicite (comme dans this.x
) ou implicite (comme dans x
x
lequel est un membre d’instance du struct). Cette règle interdit uniquement ce type d’accès et n’affecte pas si la recherche de membre entraîne un membre du struct.
Il s’agit d’une erreur au moment de la compilation pour que le corps de la fonction locale contienne une goto
instruction, une break
instruction ou une continue
instruction dont la cible se trouve en dehors du corps de la fonction locale.
Remarque : les règles ci-dessus pour
this
etgoto
reflètent les règles des fonctions anonymes dans le §12.19.3. Note de fin
Une fonction locale peut être appelée à partir d’un point lexical avant sa déclaration. Toutefois, il s’agit d’une erreur au moment de la compilation pour que la fonction soit déclarée lexicalement avant la déclaration d’une variable utilisée dans la fonction locale (§7.7).
Il s’agit d’une erreur au moment de la compilation pour une fonction locale pour déclarer un paramètre, un paramètre de type ou une variable locale portant le même nom que celui déclaré dans n’importe quel espace de déclaration de variable locale englobant.
Les corps de fonction locaux sont toujours accessibles. Le point de terminaison d’une déclaration de fonction locale est accessible si le point de départ de la déclaration de fonction locale est accessible.
Exemple : Dans l’exemple suivant, le corps de
L
l’objet est accessible même si le point de départ n’estL
pas accessible. Étant donné que le point de départ deL
n’est pas accessible, l’instruction qui suit le point de terminaison deL
n’est pas accessible :class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }
En d’autres termes, l’emplacement d’une déclaration de fonction locale n’affecte pas la portée des instructions de la fonction conteneur. exemple de fin
Si le type de l’argument à une fonction locale est dynamic
, la fonction à appeler doit être résolue au moment de la compilation, et non au moment de la compilation.
Une fonction locale ne doit pas être utilisée dans une arborescence d’expressions.
Fonction locale statique
- Peut référencer des membres statiques, des paramètres de type, des définitions constantes et des fonctions locales statiques à partir de l’étendue englobante.
- Ne doit pas référencer ou ne pas référencer
this
debase
membres d’instance à partir d’une référence implicitethis
, ni de variables locales, de paramètres ou de fonctions locales non statiques à partir de l’étendue englobante. Toutefois, toutes ces opérations sont autorisées dans unenameof()
expression.
13.7 Instructions d’expression
Une expression_statement évalue une expression donnée. La valeur calculée par l’expression, le cas échéant, est ignorée.
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
Toutes les expressions ne sont pas autorisées en tant qu’instructions.
Remarque : en particulier, les expressions telles que
x + y
etx == 1
, qui calculent simplement une valeur (qui sera ignorée), ne sont pas autorisées en tant qu’instructions. Note de fin
L’exécution d’un expression_statement évalue l’expression contenue, puis transfère le contrôle au point de terminaison de la expression_statement. Le point de terminaison d’une expression_statement est accessible si cette expression_statement est accessible.
13.8 Instructions de sélection
13.8.1 Général
Les instructions de sélection sélectionnent l’une des instructions possibles pour l’exécution en fonction de la valeur d’une expression.
selection_statement
: if_statement
| switch_statement
;
13.8.2 L’instruction if
L’instruction if
sélectionne une instruction pour l’exécution en fonction de la valeur d’une expression booléenne.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Une else
partie est associée au plus proche le plus proche du point de vue if
lexique qui est autorisé par la syntaxe.
Exemple : Par conséquent, une
if
instruction du formulaireif (x) if (y) F(); else G();
équivaut à :
if (x) { if (y) { F(); } else { G(); } }
exemple de fin
Une if
instruction est exécutée comme suit :
- Le boolean_expression (§12.24) est évalué.
- Si l’expression booléenne génère
true
, le contrôle est transféré vers la première instruction incorporée. Quand et si le contrôle atteint le point de fin de cette instruction, le contrôle est transféré au point de terminaison de l’instructionif
. - Si l’expression booléenne génère
false
et si uneelse
partie est présente, le contrôle est transféré vers la deuxième instruction incorporée. Quand et si le contrôle atteint le point de fin de cette instruction, le contrôle est transféré au point de terminaison de l’instructionif
. - Si l’expression booléenne génère
false
et si uneelse
partie n’est pas présente, le contrôle est transféré au point de terminaison de l’instructionif
.
La première instruction incorporée d’une if
instruction est accessible si l’instruction if
est accessible et que l’expression booléenne n’a pas la valeur false
constante.
La deuxième instruction incorporée d’une if
instruction, le cas échéant, est accessible si l’instruction if
est accessible et que l’expression booléenne n’a pas la valeur true
constante.
Le point de terminaison d’une if
instruction est accessible si le point de fin d’au moins une de ses instructions incorporées est accessible. En outre, le point de terminaison d’une if
instruction sans partie n’est else
accessible si l’instruction if
est accessible et que l’expression booléenne n’a pas la valeur true
constante.
13.8.3 L’instruction switch
L’instruction switch
sélectionne l’exécution d’une liste d’instructions ayant une étiquette de commutateur associée qui correspond à la valeur de l’expression de commutateur.
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
Un switch_statement se compose du mot cléswitch
, suivi d’une expression entre parenthèses (appelée expression switch), suivie d’un switch_block. Le switch_block se compose de zéro ou plusieurs switch_section, placés entre accolades. Chaque switch_section se compose d’un ou plusieurs switch_labelsuivis d’un statement_list (§13.3.2). Chaque switch_label contenant case
a un modèle associé (§11) auquel la valeur de l’expression switch est testée. Si case_guard est présente, son expression doit être implicitement convertible en type bool
et cette expression est évaluée comme une condition supplémentaire pour que le cas soit considéré comme satisfait.
Le type de gouvernance d’une switch
instruction est établi par l’expression switch.
- Si le type de l’expression de commutateur est
sbyte
, ,byte
short
ushort
int
uint
long
ulong
char
bool
string
ou un enum_type, ou s’il s’agit du type valeur nullable correspondant à l’un de ces types, il s’agit du type de gouvernance de l’instruction.switch
- Sinon, si exactement une conversion implicite définie par l’utilisateur existe du type de l’expression switch vers l’un des types de gouvernance possibles suivants :
sbyte
, ,byte
,short
,ushort
,int
,uint
long
,ulong
char
string
, ou, un type valeur nullable correspondant à l’un de ces types, le type converti est le type de gouvernance de l’instruction.switch
- Sinon, le type de gouvernance de l’instruction
switch
est le type de l’expression switch. Il s’agit d’une erreur si aucun type de ce type n’existe.
Il peut y avoir au maximum une default
étiquette dans une switch
instruction.
Il s’agit d’une erreur si le modèle d’une étiquette de commutateur n’est pas applicable (§11.2.1) au type de l’expression d’entrée.
Il s’agit d’une erreur si le modèle d’une étiquette de commutateur est subsumé par (§11.3) l’ensemble de modèles d’étiquettes de commutateur antérieures de l’instruction switch qui n’ont pas de garde-casse ou dont la protection de casse est une expression constante avec la valeur true.
Exemple :
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }
exemple de fin
Une switch
instruction est exécutée comme suit :
- L’expression switch est évaluée et convertie en type de gouvernance.
- Le contrôle est transféré en fonction de la valeur de l’expression de commutateur convertie :
- Le premier modèle lexical dans l’ensemble d’étiquettes
case
de la mêmeswitch
instruction qui correspond à la valeur de l’expression switch, et pour lequel l’expression guard est absente ou évaluée à true, provoque le transfert du contrôle vers la liste d’instructions suivant l’étiquette correspondantecase
. - Sinon, si une
default
étiquette est présente, le contrôle est transféré vers la liste d’instructions suivant l’étiquettedefault
. - Sinon, le contrôle est transféré au point de terminaison de l’instruction
switch
.
- Le premier modèle lexical dans l’ensemble d’étiquettes
Remarque : L’ordre dans lequel les modèles sont mis en correspondance au moment de l’exécution n’est pas défini. Un compilateur est autorisé (mais pas obligatoire) à faire correspondre les modèles hors ordre et à réutiliser les résultats des modèles déjà mis en correspondance pour calculer le résultat de la correspondance d’autres modèles. Néanmoins, un compilateur est requis pour déterminer le premier modèle lexical qui correspond à l’expression et pour lequel la clause de garde est absente ou qui s'évalue à
true
. Note de fin
Si le point de terminaison de la liste d’instructions d’une section switch est accessible, une erreur au moment de la compilation se produit. C’est ce qu’on appelle la règle « no fall through ».
Exemple : l’exemple
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }
est valide, car aucune section switch n’a un point de terminaison accessible. Contrairement à C et C++, l’exécution d’une section de commutateur n’est pas autorisée à « passer » à la section de commutateur suivante, et l’exemple
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }
génère une erreur au moment de la compilation. Lorsque l’exécution d’une section de commutateur doit être suivie de l’exécution d’une autre section de commutateur, une instruction ou
goto case
une instruction explicitegoto default
doit être utilisée :switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }
exemple de fin
Plusieurs étiquettes sont autorisées dans un switch_section.
Exemple : l’exemple
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }
est valide. L’exemple ne viole pas la règle « no fall through » car les étiquettes
case 2:
etdefault:
font partie de la même switch_section.exemple de fin
Remarque : la règle « aucune défaillance » empêche une classe commune de bogues qui se produisent en C et C++ lorsque
break
des instructions sont accidentellement omises. Par exemple, les sections de l’instructionswitch
ci-dessus peuvent être inversées sans affecter le comportement de l’instruction :switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }
Note de fin
Remarque : La liste d’instructions d’une section switch se termine généralement par un
break
,goto case
ougoto default
une instruction, mais toute construction qui affiche le point de terminaison de la liste d’instructions inaccessible est autorisée. Par exemple, unewhile
instruction contrôlée par l’expressiontrue
booléenne est connue pour ne jamais atteindre son point de terminaison. De même, une instruction outhrow
unereturn
instruction transfère toujours le contrôle ailleurs et n’atteint jamais son point de terminaison. Par conséquent, l’exemple suivant est valide :switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }
Note de fin
Exemple : le type de gouvernance d’une
switch
instruction peut être le typestring
. Par exemple :void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }
exemple de fin
Remarque : Comme les opérateurs d’égalité de chaîne (§12.12.8), l’instruction respecte la
switch
casse et exécute une section switch donnée uniquement si la chaîne d’expression switch correspond exactement à unecase
constante d’étiquette. Note de fin Lorsque le type d’administration d’uneswitch
instruction eststring
ou un type valeur nullable, la valeurnull
est autorisée en tant quecase
constante d’étiquette.
Les statement_listd’un switch_block peuvent contenir des instructions de déclaration (§13.6). L’étendue d’une variable locale ou d’une constante déclarée dans un bloc de commutateur est le bloc de commutateur.
Une étiquette de commutateur est accessible si au moins l’une des valeurs suivantes est vraie :
- L’expression switch est une valeur constante et l’une ou l’autre
- l’étiquette est une
case
dont le modèle correspondrait (§11.2.1) à cette valeur, et la garde de l’étiquette est absente ou non une expression constante avec la valeur false ; ou - il s’agit d’une
default
étiquette, et aucune section switch ne contient une étiquette de casse dont le modèle correspond à cette valeur, et dont la protection est absente ou une expression constante avec la valeur true.
- l’étiquette est une
- L’expression switch n’est pas une valeur constante et l’une ou l’autre
- l’étiquette est sans
case
garde ou avec un garde dont la valeur n’est pas la constante false ; ou - il s’agit d’une
default
étiquette et- l’ensemble de modèles apparaissant parmi les cas de l’instruction switch qui n’ont pas de gardes ou qui ont des gardes dont la valeur est la valeur constante vraie, n’est pas exhaustive (§11.4) pour le type de gouvernance de commutateur ; ou
- le type de gouvernance de commutateur est un type Nullable et l’ensemble de modèles apparaissant parmi les cas de l’instruction switch qui n’ont pas de gardes ou qui ont des gardes dont la valeur est la valeur true ne contient pas de modèle qui correspondrait à la valeur
null
.
- l’étiquette est sans
- L’étiquette de commutateur est référencée par une instruction accessible
goto case
ougoto default
accessible.
La liste des instructions d’une section switch donnée est accessible si l’instruction switch
est accessible et que la section switch contient une étiquette de commutateur accessible.
Le point de terminaison d’une switch
instruction est accessible si l’instruction switch est accessible et qu’au moins l’une des valeurs suivantes est vraie :
- L’instruction
switch
contient une instruction accessiblebreak
qui quitte l’instructionswitch
. - Aucune étiquette n’est
default
présente et l’une ou l’autre- L’expression de commutateur est une valeur non constante, et l’ensemble de modèles apparaissant parmi les cas de l’instruction switch qui n’ont pas de gardes ou qui ont des gardes dont la valeur est vraie, n’est pas exhaustive (§11.4) pour le type de gouvernance de commutateur.
- L’expression de commutateur est une valeur non constante d’un type nullable, et aucun modèle n’apparaît parmi les cas de l’instruction switch qui n’ont pas de gardes ou qui ont des gardes dont la valeur est la valeur true correspond à la valeur
null
. - L’expression switch est une valeur constante et aucune étiquette sans
case
garde ou dont la garde est la constante true correspondrait à cette valeur.
Exemple : Le code suivant montre une utilisation succincte de la
when
clause :static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }
La casse var correspond
null
, la chaîne vide ou toute chaîne qui contient uniquement de l’espace blanc. exemple de fin
Instructions d’itération 13.9
13.9.1 Général
Les instructions d’itération exécutent à plusieurs reprises une instruction incorporée.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 L’instruction while
L’instruction while
exécute conditionnellement une instruction incorporée zéro ou plusieurs fois.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Une while
instruction est exécutée comme suit :
- Le boolean_expression (§12.24) est évalué.
- Si l’expression booléenne génère
true
, le contrôle est transféré à l’instruction incorporée. Quand et si le contrôle atteint le point de terminaison de l’instruction incorporée (éventuellement à partir de l’exécution d’unecontinue
instruction), le contrôle est transféré au début de l’instructionwhile
. - Si l’expression booléenne génère
false
, le contrôle est transféré au point de terminaison de l’instructionwhile
.
Dans l’instruction incorporée d’une while
instruction, une break
instruction (§13.10.2) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction while
(donc mettre fin à l’itération de l’instruction incorporée) et une continue
instruction (§13.10.3) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction incorporée (effectuant ainsi une autre itération de l’instruction while
).
L’instruction incorporée d’une while
instruction est accessible si l’instruction while
est accessible et que l’expression booléenne n’a pas la valeur false
constante.
Le point de terminaison d’une while
instruction est accessible si au moins l’un des éléments suivants est vrai :
- L’instruction
while
contient une instruction accessiblebreak
qui quitte l’instructionwhile
. - L’instruction
while
est accessible et l’expression booléenne n’a pas la valeurtrue
constante.
13.9.3 L’instruction do
L’instruction do
exécute de façon conditionnelle une instruction incorporée une ou plusieurs fois.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Une do
instruction est exécutée comme suit :
- Le contrôle est transféré vers l’instruction incorporée.
- Quand et si le contrôle atteint le point de terminaison de l’instruction incorporée (éventuellement à partir de l’exécution d’une
continue
instruction), le boolean_expression (§12.24) est évalué. Si l’expression booléenne génèretrue
, le contrôle est transféré au début de l’instructiondo
. Sinon, le contrôle est transféré au point de terminaison de l’instructiondo
.
Dans l’instruction incorporée d’une do
instruction, une break
instruction (§13.10.2) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction do
(donc mettre fin à l’itération de l’instruction incorporée) et une continue
instruction (§13.10.3) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction incorporée (effectuant ainsi une autre itération de l’instruction do
).
L’instruction incorporée d’une do
instruction est accessible si l’instruction do
est accessible.
Le point de terminaison d’une do
instruction est accessible si au moins l’un des éléments suivants est vrai :
- L’instruction
do
contient une instruction accessiblebreak
qui quitte l’instructiondo
. - Le point de terminaison de l’instruction incorporée est accessible et l’expression booléenne n’a pas la valeur
true
constante.
13.9.4 L’instruction for
L’instruction for
évalue une séquence d’expressions d’initialisation, puis, alors qu’une condition est vraie, exécute à plusieurs reprises une instruction incorporée et évalue une séquence d’expressions d’itération.
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
Le for_initializer, le cas échéant, se compose d’un local_variable_declaration (§13.6.2) ou d’une liste de statement_expressions (§13.7) séparés par des virgules. L’étendue d’une variable locale déclarée par un for_initializer est la for_initializer, for_condition, for_iterator et embedded_statement.
Le for_condition, le cas échéant, doit être un boolean_expression (§12.24).
Le for_iterator, le cas échéant, se compose d’une liste de statement_expressions (§13.7) séparés par des virgules.
Une for
instruction est exécutée comme suit :
- Si une for_initializer est présente, les initialiseurs de variables ou les expressions d’instruction sont exécutés dans l’ordre dans lequel ils sont écrits. Cette étape n’est effectuée qu’une seule fois.
- Si une for_condition est présente, elle est évaluée.
- Si le for_condition n’est pas présent ou si l’évaluation génère
true
, le contrôle est transféré à l’instruction incorporée. Quand et si le contrôle atteint le point de terminaison de l’instruction incorporée (éventuellement à partir de l’exécution d’unecontinue
instruction), les expressions de l’for_iterator, le cas échéant, sont évaluées en séquence, puis une autre itération est effectuée, en commençant par l’évaluation de la for_condition à l’étape ci-dessus. - Si le for_condition est présent et que l’évaluation génère
false
, le contrôle est transféré au point de terminaison de l’instructionfor
.
Dans l’instruction incorporée d’une for
instruction, une break
instruction (§13.10.2) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction for
(ainsi mettre fin à l’itération de l’instruction incorporée) et une instruction (continue
) peut être utilisée pour transférer le contrôle vers le point de terminaison de l’instruction incorporée (par conséquent, l’exécution de la for_iterator et l’exécution d’une autre itération de l’instructionfor
, à partir du for_condition).
L’instruction incorporée d’une for
instruction est accessible si l’une des valeurs suivantes est vraie :
- L’instruction
for
est accessible et aucune for_condition n’est présente. - L’instruction
for
est accessible et une for_condition est présente et n’a pas la valeurfalse
constante.
Le point de terminaison d’une for
instruction est accessible si au moins l’un des éléments suivants est vrai :
- L’instruction
for
contient une instruction accessiblebreak
qui quitte l’instructionfor
. - L’instruction
for
est accessible et une for_condition est présente et n’a pas la valeurtrue
constante.
13.9.5 L’instruction foreach
L’instruction foreach
énumère les éléments d’une collection, en exécutant une instruction incorporée pour chaque élément de la collection.
foreach_statement
: 'foreach' '(' ref_kind? local_variable_type identifier 'in'
expression ')' embedded_statement
;
L’local_variable_type et l’identificateur d’une instruction foreach déclarent la variable d’itération de l’instruction. Si l’identificateur var
est donné en tant que local_variable_type et qu’aucun type nommé var
n’est dans l’étendue, la variable d’itération est considérée comme une variable d’itération implicitement typée, et son type est considéré comme le type d’élément de l’instruction foreach
, comme indiqué ci-dessous.
Si le foreach_statement contient à la fois ou non ref
et readonly
que la variable d’itération désigne une variable traitée en lecture seule. Sinon, si foreach_statement contient ref
sans readonly
, la variable d’itération désigne une variable pouvant être accessible en écriture.
La variable d’itération correspond à une variable locale avec une étendue qui s’étend sur l’instruction incorporée. Pendant l’exécution d’une foreach
instruction, la variable d’itération représente l’élément de collection pour lequel une itération est en cours d’exécution. Si la variable d’itération désigne une variable en lecture seule, une erreur au moment de la compilation se produit si l’instruction incorporée tente de la modifier (via l’affectation ou les opérateurs) ou de la ++
--
transmettre en tant que paramètre de référence ou de sortie.
Dans la section suivante, pour la concision, IEnumerable
IEnumerator
et IEnumerable<T>
IEnumerator<T>
reportez-vous aux types correspondants System.Collections
dans les espaces de noms et System.Collections.Generic
.
Le traitement au moment de la compilation d’une foreach
instruction détermine d’abord le type de collection, le type d’énumérateur et le type d’itération de l’expression. Cette détermination se poursuit comme suit :
- Si le type
X
d’expression est un type de tableau, il existe une conversion de référence implicite de X vers l’interfaceIEnumerable
(carSystem.Array
implémente cette interface). Le type de collection est l’interfaceIEnumerable
, le type d’énumérateur est l’interfaceIEnumerator
et le type d’itération est le type d’élément du typeX
de tableau. - Si le type
X
d’expression estdynamic
alors une conversion implicite de l’expression vers l’interfaceIEnumerable
(§10.2.10). Le type de collection est l’interfaceIEnumerable
et le type d’énumérateur est l’interfaceIEnumerator
. Si l’identificateurvar
est donné en tant que local_variable_type le type d’itération estdynamic
, sinon il estobject
. - Sinon, déterminez si le type
X
a une méthode appropriéeGetEnumerator
:- Effectuez une recherche de membre sur le type
X
avec l’identificateurGetEnumerator
et aucun argument de type. Si la recherche de membre ne produit pas de correspondance, ou génère une ambiguïté ou produit une correspondance qui n’est pas un groupe de méthodes, recherchez une interface énumérable, comme décrit ci-dessous. Il est recommandé d’émettre un avertissement si la recherche de membre produit quoi que ce soit sauf un groupe de méthodes ou aucune correspondance. - Effectuez une résolution de surcharge à l’aide du groupe de méthodes résultant et d’une liste d’arguments vides. Si la résolution de surcharge n’entraîne aucune ambiguïté, une ambiguïté ou entraîne une méthode optimale, mais que cette méthode est statique ou non publique, vérifiez une interface énumérable comme décrit ci-dessous. Il est recommandé d’émettre un avertissement si la résolution de surcharge produit quelque chose sauf une méthode d’instance publique non ambiguë ou aucune méthode applicable.
- Si le type
E
de retour de laGetEnumerator
méthode n’est pas une classe, un struct ou un type d’interface, une erreur est générée et aucune autre étape n’est effectuée. - La recherche de membre est effectuée avec
E
l’identificateurCurrent
et aucun argument de type. Si la recherche de membre ne produit aucune correspondance, le résultat est une erreur ou le résultat est autre chose qu’une propriété d’instance publique qui autorise la lecture, une erreur est générée et aucune autre étape n’est effectuée. - La recherche de membre est effectuée avec
E
l’identificateurMoveNext
et aucun argument de type. Si la recherche de membre ne produit aucune correspondance, le résultat est une erreur ou le résultat est autre chose qu’un groupe de méthodes, une erreur est générée et aucune autre étape n’est effectuée. - La résolution de surcharge est effectuée sur le groupe de méthodes avec une liste d’arguments vide. Si la résolution de surcharge n’entraîne aucune méthode applicable, génère une ambiguïté ou génère une seule meilleure méthode, mais cette méthode est statique ou non publique, ou son type de retour n’est pas
bool
, une erreur est générée et aucune autre étape n’est effectuée. - Le type de collection est
X
, le type d’énumérateur estE
, et le type d’itération est le type de laCurrent
propriété. LaCurrent
propriété peut inclure leref
modificateur, auquel cas l’expression retournée est une variable_reference (§9.5) qui est éventuellement en lecture seule.
- Effectuez une recherche de membre sur le type
- Sinon, recherchez une interface énumérable :
- Si, parmi tous les types
Tᵢ
pour lesquels il existe une conversion implicite deX
versIEnumerable<Tᵢ>
, il existe un typeT
unique quiT
n’est pasdynamic
et pour tous les autresTᵢ
il y a une conversion implicite deIEnumerable<T>
versIEnumerable<Tᵢ>
, alors le type de collection est l’interfaceIEnumerable<T>
, le type d’énumérateur est l’interfaceIEnumerator<T>
, et le type d’itération estT
. - Sinon, s’il existe plusieurs types de ce type
T
, une erreur est générée et aucune autre procédure n’est effectuée. - Sinon, s’il existe une conversion implicite depuis
X
l’interfaceSystem.Collections.IEnumerable
, le type de collection est cette interface, le type d’énumérateur est l’interfaceSystem.Collections.IEnumerator
et le type d’itération estobject
. - Sinon, une erreur est générée et aucune autre procédure n’est effectuée.
- Si, parmi tous les types
Les étapes ci-dessus, si elles réussissent, produisent sans ambiguïté un type C
de collection, un type d’énumérateur et un type E
T
d’itération , ref T
ou ref readonly T
. Instruction foreach
du formulaire
foreach (V v in x) «embedded_statement»
équivaut ensuite à :
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
La variable e
n’est pas visible ou accessible à l’expression x
ou à l’instruction incorporée ou à tout autre code source du programme. La variable v
est en lecture seule dans l’instruction incorporée. S’il n’existe pas de conversion explicite (§10.3) de T
(type d’itération) vers V
(le local_variable_type dans l’instruction foreach
), une erreur est générée et aucune autre étape n’est effectuée.
Lorsque la variable d’itération est une variable de référence (§9.7), une foreach
instruction du formulaire
foreach (ref V v in x) «embedded_statement»
équivaut ensuite à :
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
La variable e
n’est pas visible ou accessible à l’expression x
ou à l’instruction incorporée ou à tout autre code source du programme. La variable v
de référence est en lecture-écriture dans l’instruction incorporée, mais v
ne doit pas être réaffectée (§12.21.3). S’il n’existe pas de conversion d’identité (§10.2.2) de T
(type d’itération) vers V
(le local_variable_type dans l’instruction foreach
), une erreur est générée et aucune autre étape n’est effectuée.
Une foreach
instruction du formulaire foreach (ref readonly V v in x) «embedded_statement»
a un formulaire équivalent similaire, mais la variable v
de référence se trouve ref readonly
dans l’instruction incorporée et ne peut donc pas être réaffectée ou réaffectée.
Remarque : Si
x
la valeurnull
est définie, uneSystem.NullReferenceException
valeur est levée au moment de l’exécution. Note de fin
Une implémentation est autorisée à implémenter une foreach_statement donnée différemment ; par exemple, pour des raisons de performances, tant que le comportement est cohérent avec l’expansion ci-dessus.
Le placement de v
la while
boucle est important pour la façon dont elle est capturée (§12.19.6.2) par toute fonction anonyme se produisant dans le embedded_statement.
Exemple :
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();
Si
v
dans le formulaire développé ont été déclarés en dehors de lawhile
boucle, il serait partagé entre toutes les itérations, et sa valeur après lafor
boucle serait la valeur finale,13
c’est-à-dire ce que l’appel d’imprimerf
. Au lieu de cela, étant donné que chaque itération a sa propre variablev
, celle capturée parf
la première itération continuera à contenir la valeur7
, c’est-à-dire ce qui sera imprimé. (Notez que les versions antérieures de C# déclaréesv
en dehors de lawhile
boucle.)exemple de fin
Le corps du finally
bloc est construit en suivant les étapes suivantes :
S’il existe une conversion implicite de
E
l’interfaceSystem.IDisposable
,S’il s’agit
E
d’un type valeur non nullable, lafinally
clause est étendue à l’équivalent sémantique de :finally { ((System.IDisposable)e).Dispose(); }
Sinon, la
finally
clause est étendue à l’équivalent sémantique de :finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
sauf que s’il s’agit
E
d’un type valeur ou d’un paramètre de type instancié en type valeur, la conversion d’unee
System.IDisposable
valeur ne provoque pas la boxing.
Sinon, s’il s’agit
E
d’un type scellé, lafinally
clause est développée dans un bloc vide :finally {}
Sinon, la
finally
clause est développée pour :finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
La variable d
locale n’est pas visible ou accessible à tout code utilisateur. En particulier, il n’est pas en conflit avec une autre variable dont l’étendue inclut le finally
bloc.
L’ordre dans lequel foreach
traverse les éléments d’un tableau est le suivant : Pour les éléments de tableaux unidimensionnels sont parcourus dans l’ordre croissant d’index, à partir de l’index 0 et se terminant par l’index Length – 1
. Pour les tableaux multidimensionnels, les éléments sont parcourus de sorte que les index de la dimension la plus à droite soient augmentés en premier, puis la dimension gauche suivante, et ainsi de suite à gauche.
Exemple : L’exemple suivant imprime chaque valeur dans un tableau à deux dimensions, dans l’ordre des éléments :
class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) { Console.Write($"{elementValue} "); } Console.WriteLine(); } }
La sortie produite est la suivante :
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9
exemple de fin
Exemple : dans l’exemple suivant
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
le type d’est
n
déduitint
comme étant , le type d’itération denumbers
.exemple de fin
13.10 Instructions jump
13.10.1 Général
Instructions de saut sans condition de transfert de contrôle.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
L’emplacement auquel une instruction de saut transfère le contrôle est appelé la cible de l’instruction de saut.
Lorsqu’une instruction de saut se produit dans un bloc et que la cible de cette instruction de saut se trouve en dehors de ce bloc, l’instruction de saut est dite pour quitter le bloc. Bien qu’une instruction de saut puisse transférer le contrôle hors d’un bloc, elle ne peut jamais transférer le contrôle dans un bloc.
L’exécution d’instructions de saut est compliquée par la présence d’instructions intermédiaires try
. En l’absence de ces try
instructions, une instruction de saut transfère inconditionnellement le contrôle de l’instruction jump à sa cible. En présence de ces instructions intermédiaires try
, l’exécution est plus complexe. Si l’instruction jump quitte un ou plusieurs try
blocs avec des blocs associés finally
, le contrôle est initialement transféré vers le finally
bloc de l’instruction la plus try
interne. Quand et si le contrôle atteint le point de terminaison d’un finally
bloc, le contrôle est transféré vers le finally
bloc de l’instruction englobante try
suivante. Ce processus est répété jusqu’à ce que les finally
blocs de toutes les instructions intermédiaires try
aient été exécutés.
Exemple : dans le code suivant
class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }
les
finally
blocs associés à deuxtry
instructions sont exécutés avant le transfert du contrôle vers la cible de l’instruction jump. La sortie produite est la suivante :Before break Innermost finally block Outermost finally block After break
exemple de fin
13.10.2 L’instruction break
L’instruction break
quitte l’instruction englobante switch
la plus proche, , while
do
, for
ou foreach
l’instruction.
break_statement
: 'break' ';'
;
La cible d’une break
instruction est le point de terminaison de l’instruction englobante la plus switch
proche , , while
do
, for
ou foreach
de l’instruction. Si une instruction n’est pas entourée d’une break
switch
instruction , ou while
do
for
foreach
d’une instruction, une erreur au moment de la compilation se produit.
Lorsque plusieurs switch
instructions sont while
do
for
foreach
imbriquées entre elles, une break
instruction s’applique uniquement à l’instruction la plus interne. Pour transférer le contrôle sur plusieurs niveaux d’imbrication, une goto
instruction (§13.10.4) doit être utilisée.
Une break
instruction ne peut pas quitter un finally
bloc (§13.11). Lorsqu’une break
instruction se produit dans un finally
bloc, la cible de l’instruction doit se trouver dans le même break
bloc ; sinon, une erreur au moment de la finally
compilation se produit.
Une break
instruction est exécutée comme suit :
- Si l’instruction
break
quitte un ou plusieurstry
blocs avec des blocs associésfinally
, le contrôle est initialement transféré vers lefinally
bloc de l’instruction la plustry
interne. Quand et si le contrôle atteint le point de terminaison d’unfinally
bloc, le contrôle est transféré vers lefinally
bloc de l’instruction englobantetry
suivante. Ce processus est répété jusqu’à ce que lesfinally
blocs de toutes les instructions intermédiairestry
aient été exécutés. - Le contrôle est transféré vers la cible de l’instruction
break
.
Étant donné qu’une break
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une break
instruction n’est jamais accessible.
13.10.3 L’instruction continue
L’instruction continue
démarre une nouvelle itération de l’instruction englobante la plus while
proche, ou do
for
foreach
de l’instruction.
continue_statement
: 'continue' ';'
;
La cible d’une continue
instruction est le point de terminaison de l’instruction incorporée de l’instruction englobante la plus while
proche, do
ou for
foreach
de l’instruction. Si une continue
instruction n’est pas entourée d’une while
instruction , do
ou for
foreach
d’une instruction, une erreur au moment de la compilation se produit.
Lorsque plusieurs while
instructions sont do
for
foreach
imbriquées entre elles, une continue
instruction s’applique uniquement à l’instruction la plus interne. Pour transférer le contrôle sur plusieurs niveaux d’imbrication, une goto
instruction (§13.10.4) doit être utilisée.
Une continue
instruction ne peut pas quitter un finally
bloc (§13.11). Lorsqu’une continue
instruction se produit dans un finally
bloc, la cible de l’instruction doit se trouver dans le même continue
bloc ; sinon, une erreur au moment de la finally
compilation se produit.
Une continue
instruction est exécutée comme suit :
- Si l’instruction
continue
quitte un ou plusieurstry
blocs avec des blocs associésfinally
, le contrôle est initialement transféré vers lefinally
bloc de l’instruction la plustry
interne. Quand et si le contrôle atteint le point de terminaison d’unfinally
bloc, le contrôle est transféré vers lefinally
bloc de l’instruction englobantetry
suivante. Ce processus est répété jusqu’à ce que lesfinally
blocs de toutes les instructions intermédiairestry
aient été exécutés. - Le contrôle est transféré vers la cible de l’instruction
continue
.
Étant donné qu’une continue
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une continue
instruction n’est jamais accessible.
13.10.4 L’instruction goto
L’instruction transfère le goto
contrôle à une instruction marquée par une étiquette.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
La cible d’une goto
instruction d’identificateur est l’instruction étiquetée avec l’étiquette donnée. Si une étiquette portant le nom donné n’existe pas dans le membre de la fonction actuelle ou si l’instruction goto
n’est pas dans l’étendue de l’étiquette, une erreur au moment de la compilation se produit.
Remarque : Cette règle autorise l’utilisation d’une
goto
instruction pour transférer le contrôle hors d’une étendue imbriquée, mais pas dans une étendue imbriquée. Dans l’exempleclass Test { static void Main(string[] args) { string[,] table = { {"Red", "Blue", "Green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) { for (colm = 0; colm <= 2; ++colm) { if (str == table[row,colm]) { goto done; } } } Console.WriteLine($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }
une
goto
instruction est utilisée pour transférer le contrôle hors d’une étendue imbriquée.Note de fin
La cible d’une goto case
instruction est la liste d’instructions dans l’instruction englobante immédiatement switch
(§13.8.3) qui contient une case
étiquette avec un modèle constant de la valeur constante donnée et sans garde. Si l’instruction goto case
n’est pas entourée d’une switch
instruction, si l’instruction englobante la plus switch
proche ne contient pas de case
telles instructions, ou si le constant_expressionswitch
compilation se produit.
La cible d’une goto default
instruction est la liste d’instructions dans l’instruction englobante immédiatement switch
(§13.8.3), qui contient une default
étiquette. Si l’instruction n’est pas entourée d’une goto default
instruction ou si l’instruction englobante la plus switch
proche ne contient pas d’étiquetteswitch
, une erreur au moment de la default
compilation se produit.
Une goto
instruction ne peut pas quitter un finally
bloc (§13.11). Lorsqu’une goto
instruction se produit dans un finally
bloc, la cible de l’instruction doit se trouver dans le même goto
bloc ou une erreur au moment de la finally
compilation se produit.
Une goto
instruction est exécutée comme suit :
- Si l’instruction
goto
quitte un ou plusieurstry
blocs avec des blocs associésfinally
, le contrôle est initialement transféré vers lefinally
bloc de l’instruction la plustry
interne. Quand et si le contrôle atteint le point de terminaison d’unfinally
bloc, le contrôle est transféré vers lefinally
bloc de l’instruction englobantetry
suivante. Ce processus est répété jusqu’à ce que lesfinally
blocs de toutes les instructions intermédiairestry
aient été exécutés. - Le contrôle est transféré vers la cible de l’instruction
goto
.
Étant donné qu’une goto
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une goto
instruction n’est jamais accessible.
13.10.5 L’instruction return
L’instruction return
retourne le contrôle à l’appelant actuel du membre de fonction dans lequel l’instruction return apparaît, en retournant éventuellement une valeur ou un variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Une return_statement sans expression est appelée une valeur de retour sans valeur ; une expression contenant ref
est appelée retour par ref ; et une expression contenant uniquement est appelée retour par valeur.
Il s’agit d’une erreur au moment de la compilation d’utiliser une valeur de retour sans valeur d’une méthode déclarée en tant que retour par valeur ou return-by-ref (§15.6.1).
Il s’agit d’une erreur au moment de la compilation d’utiliser un retour par ref à partir d’une méthode déclarée comme étant return-no-value ou return-by-value.
Il s’agit d’une erreur au moment de la compilation pour utiliser une valeur renvoyée par valeur à partir d’une méthode déclarée comme étant return-no-value ou return-by-ref.
Il s’agit d’une erreur au moment de la compilation d’utiliser un retour par réf si l’expression n’est pas un variable_reference ou est une référence à une variable dont le contexte ref-safe n’est pas caller-context (§9.7.2).
Il s’agit d’une erreur au moment de la compilation d’utiliser un retour par ref à partir d’une méthode déclarée avec la method_modifierasync
.
Un membre de fonction est dit pour calculer une valeur s’il s’agit d’une méthode de retour par valeur (§15.6.11), d’un accesseur get de retour par valeur d’une propriété ou d’un indexeur, ou d’un opérateur défini par l’utilisateur. Les membres de fonction qui sont des retours sans valeur ne calculent pas de valeur et sont des méthodes avec le type void
de retour effectif, définissez les accesseurs des propriétés et des indexeurs, ajoutez et supprimez des accesseurs d’événements, des constructeurs d’instance, des constructeurs statiques et des finaliseurs. Les membres de fonction qui sont retournés par ref ne calculent pas de valeur.
Pour un retour par valeur, une conversion implicite (§10.2) doit exister du type d’expression au type de retour effectif (§15.6.11) du membre de fonction conteneur. Pour un retour par réf, une conversion d’identité (§10.2.2) doit exister entre le type d’expression et le type de retour effectif du membre de fonction conteneur.
return
les instructions peuvent également être utilisées dans le corps des expressions de fonction anonyme (§12.19) et participer à la détermination des conversions existantes pour ces fonctions (§10.7.1).
Il s’agit d’une erreur au moment de la compilation pour qu’une return
instruction apparaisse dans un finally
bloc (§13.11).
Une return
instruction est exécutée comme suit :
- Pour un retour par valeur, l’expression est évaluée et sa valeur est convertie en type de retour effectif de la fonction conteneur par une conversion implicite. Le résultat de la conversion devient la valeur de résultat produite par la fonction. Pour un retour par ref, l’expression est évaluée et le résultat doit être classé comme variable. Si la méthode englobante return-by-ref inclut
readonly
, la variable résultante est en lecture seule. - Si l’instruction est entourée d’un ou plusieurs
return
try
blocs avec des blocs associéscatch
, lefinally
contrôle est initialement transféré vers lefinally
bloc de l’instruction la plustry
interne. Quand et si le contrôle atteint le point de terminaison d’unfinally
bloc, le contrôle est transféré vers lefinally
bloc de l’instruction englobantetry
suivante. Ce processus est répété jusqu’à ce que lesfinally
blocs de toutes les instructions englobantestry
aient été exécutés. - Si la fonction conteneur n’est pas une fonction asynchrone, le contrôle est retourné à l’appelant de la fonction conteneur, ainsi que la valeur de résultat, le cas échéant.
- Si la fonction conteneur est une fonction asynchrone, le contrôle est retourné à l’appelant actuel et la valeur du résultat, le cas échéant, est enregistrée dans la tâche de retour, comme décrit dans (§15.15.3).
Étant donné qu’une return
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une return
instruction n’est jamais accessible.
13.10.6 L’instruction throw
L’instruction throw
lève une exception.
throw_statement
: 'throw' expression? ';'
;
Une throw
instruction avec une expression lève une exception produite en évaluant l’expression. L’expression doit être implicitement convertible System.Exception
en , et le résultat de l’évaluation de l’expression est converti en System.Exception
avant d’être levée. Si le résultat de la conversion est null
, il System.NullReferenceException
est levée à la place.
Une throw
instruction sans expression ne peut être utilisée que dans un catch
bloc, auquel cas, cette instruction lève à nouveau l’exception actuellement gérée par ce catch
bloc.
Étant donné qu’une throw
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une throw
instruction n’est jamais accessible.
Lorsqu’une exception est levée, le contrôle est transféré vers la première catch
clause d’une instruction englobante try
qui peut gérer l’exception. Le processus qui se déroule du point de l’exception levée au point de transfert du contrôle vers un gestionnaire d’exceptions approprié est appelé propagation d’exception. La propagation d’une exception consiste à évaluer à plusieurs reprises les étapes suivantes jusqu’à ce qu’une catch
clause correspondant à l’exception soit trouvée. Dans cette description, le point de levée est initialement l’emplacement auquel l’exception est levée. Ce comportement est spécifié dans (§21.4).
Dans le membre de fonction actuel, chaque
try
instruction qui entoure le point de levée est examinée. Pour chaque instructionS
, en commençant par l’instruction la plustry
interne et se terminant par l’instruction la plustry
externe, les étapes suivantes sont évaluées :- Si le
try
bloc d’entoureS
le point de levée et s’ilS
comporte une ou plusieurscatch
clauses, lescatch
clauses sont examinées afin de localiser un gestionnaire approprié pour l’exception. La premièrecatch
clause qui spécifie un typeT
d’exception (ou un paramètre de type qui, au moment de l’exécution, désigne un typeT
d’exception) de sorte que le type d’exécution deE
dérivesT
est considéré comme une correspondance. Si la clause contient un filtre d’exception, l’objet exception est affecté à la variable d’exception et le filtre d’exception est évalué. Lorsqu’une clause contient uncatch
filtre d’exception, cettecatch
clause est considérée comme une correspondance si le filtre d’exception est évalué àtrue
. Une clause généralecatch
(§13.11) est considérée comme une correspondance pour tout type d’exception. Si une clause correspondantecatch
se trouve, la propagation de l’exception est terminée en transférant le contrôle vers le bloc de cettecatch
clause. - Sinon, si le
try
bloc ou uncatch
bloc d’entoure le point deS
levée et s’ilS
a unfinally
bloc, le contrôle est transféré vers lefinally
bloc. Si le blocfinally
lève une autre exception, le traitement de l’exception actuelle est terminé. Sinon, lorsque le contrôle atteint le point de terminaison dufinally
bloc, le traitement de l’exception actuelle est continué.
- Si le
Si un gestionnaire d’exceptions n’était pas situé dans l’appel de fonction actuel, l’appel de fonction est arrêté et l’un des événements suivants se produit :
Si la fonction actuelle n’est pas asynchrone, les étapes ci-dessus sont répétées pour l’appelant de la fonction avec un point de levée correspondant à l’instruction à partir de laquelle le membre de la fonction a été appelé.
Si la fonction actuelle est asynchrone et retourne une tâche, l’exception est enregistrée dans la tâche de retour, qui est placée dans un état défectueux ou annulé, comme décrit dans le §15.15.3.
Si la fonction actuelle est asynchrone et
void
-retournant, le contexte de synchronisation du thread actuel est notifié comme décrit dans le §15.15.4.
Si le traitement des exceptions met fin à tous les appels de membre de fonction dans le thread actuel, indiquant que le thread n’a pas de gestionnaire pour l’exception, le thread est lui-même arrêté. L’impact de cette terminaison est défini par l’implémentation.
13.11 L’instruction try
L’instruction try
fournit un mécanisme permettant d’intercepter les exceptions qui se produisent pendant l’exécution d’un bloc. En outre, l’instruction try
permet de spécifier un bloc de code qui est toujours exécuté lorsque le contrôle quitte l’instruction try
.
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Une try_statement se compose du mot clé try
suivi d’un bloc, puis de zéro ou plus catch_clauses, puis d’un finally_clause facultatif. Il y aura au moins un catch_clause ou un finally_clause.
Dans un exception_specifier le type, ou sa classe de base effective s’il s’agit d’un type_parameter, doit être System.Exception
ou un type qui en dérive.
Lorsqu’une catch
clause spécifie à la fois un class_type et un identificateur, une variable d’exception du nom et du type donnés est déclarée. La variable d’exception est introduite dans l’espace de déclaration de la specific_catch_clause (§7.3). Pendant l’exécution du exception_filtercatch
bloc, la variable d’exception représente l’exception en cours de traitement. À des fins de vérification de l’affectation définitive, la variable d’exception est considérée comme affectée définitivement dans toute sa portée.
Sauf si une catch
clause inclut un nom de variable d’exception, il est impossible d’accéder à l’objet d’exception dans le filtre et catch
le bloc.
Une catch
clause qui ne spécifie ni un type d’exception ni un nom de variable d’exception est appelée clause générale catch
. Une try
déclaration ne peut avoir qu’une clause générale catch
et, si elle est présente, il s’agit de la dernière catch
clause.
Remarque : Certains langages de programmation peuvent prendre en charge les exceptions qui ne sont pas représentées en tant qu’objet dérivé de
System.Exception
, bien que ces exceptions ne puissent jamais être générées par du code C#. Une clause généralecatch
peut être utilisée pour intercepter ces exceptions. Par conséquent, une clause généralecatch
est sémantiquement différente de celle qui spécifie le typeSystem.Exception
, dans ce que l’ancien peut également intercepter les exceptions d’autres langues. Note de fin
Pour localiser un gestionnaire pour une exception, catch
les clauses sont examinées dans l’ordre lexical. Si une catch
clause spécifie un type mais qu’aucun filtre d’exception n’est spécifié, il s’agit d’une erreur au moment de la compilation pour une clause ultérieure catch
de la même try
instruction pour spécifier un type identique ou dérivé de ce type.
Remarque : sans cette restriction, il serait possible d’écrire des clauses inaccessibles
catch
. Note de fin
Dans un catch
bloc, une throw
instruction (§13.10.6) sans expression ne peut être utilisée pour lever à nouveau l’exception interceptée par le catch
bloc. Les affectations à une variable d’exception ne modifient pas l’exception qui est levée à nouveau.
Exemple : dans le code suivant
class Test { static void F() { try { G(); } catch (Exception e) { Console.WriteLine("Exception in F: " + e.Message); e = new Exception("F"); throw; // re-throw } } static void G() => throw new Exception("G"); static void Main() { try { F(); } catch (Exception e) { Console.WriteLine("Exception in Main: " + e.Message); } } }
la méthode
F
intercepte une exception, écrit des informations de diagnostic dans la console, modifie la variable d’exception et lève à nouveau l’exception. L’exception qui est levée à nouveau est l’exception d’origine, de sorte que la sortie produite est :Exception in F: G Exception in Main: G
Si le premier
catch
bloc avait été levéee
au lieu de réinscrire l’exception actuelle, la sortie produite serait la suivante :Exception in F: G Exception in Main: F
exemple de fin
Il s’agit d’une erreur au moment de la compilation pour un break
, continue
ou goto
une instruction pour transférer le contrôle hors d’un finally
bloc. Lorsqu’une break
instruction ou une continue
goto
instruction se produit dans un finally
bloc, la cible de l’instruction doit se trouver dans le même finally
bloc ou une erreur au moment de la compilation se produit.
Il s’agit d’une erreur au moment de la compilation pour qu’une return
instruction se produise dans un finally
bloc.
Lorsque l’exécution atteint une try
instruction, le contrôle est transféré vers le try
bloc. Si le contrôle atteint le point de terminaison du try
bloc sans qu’une exception soit propagée, le contrôle est transféré vers le finally
bloc s’il en existe un. Si aucun bloc n’existe finally
, le contrôle est transféré au point de terminaison de l’instruction try
.
Si une exception a été propagée, les catch
clauses, le cas échéant, sont examinées dans l’ordre lexical pour rechercher la première correspondance pour l’exception. La recherche d’une clause correspondante catch
se poursuit avec tous les blocs englobants, comme décrit dans le §13.10.6. Une catch
clause est une correspondance si le type d’exception correspond à une exception_specifier et qu’une exception_filter est vraie. Une catch
clause sans exception_specifier correspond à un type d’exception. Le type d’exception correspond à la exception_specifier lorsque l’exception_specifierspécifie le type d’exception ou un type de base du type d’exception. Si la clause contient un filtre d’exception, l’objet exception est affecté à la variable d’exception et le filtre d’exception est évalué.
Si une exception a été propagée et qu’une clause correspondante catch
est trouvée, le contrôle est transféré vers le premier bloc correspondant catch
. Si le contrôle atteint le point de terminaison du catch
bloc sans qu’une exception soit propagée, le contrôle est transféré vers le finally
bloc s’il en existe un. Si aucun bloc n’existe finally
, le contrôle est transféré au point de terminaison de l’instruction try
. Si une exception a été propagée à partir du catch
bloc, le contrôle transfère au finally
bloc s’il en existe une. L’exception est propagée à l’instruction englobante try
suivante.
Si une exception a été propagée et qu’aucune clause correspondante catch
n’est trouvée, contrôle les transferts vers le finally
bloc, s’il existe. L’exception est propagée à l’instruction englobante try
suivante.
Les instructions d’un bloc finally
sont toujours exécutées lorsque le contrôle quitte une instruction try
. Cela est vrai si le transfert de contrôle se produit à la suite de l’exécution normale, à la suite de l’exécution d’une break
instruction , continue
ou goto
return
en raison de la propagation d’une exception hors de l’instructiontry
. Si le contrôle atteint le point de terminaison du finally
bloc sans qu’une exception soit propagée, le contrôle est transféré au point de terminaison de l’instruction try
.
Si une exception est levée pendant l’exécution d’un finally
bloc et qu’elle n’est pas interceptée dans le même finally
bloc, l’exception est propagée à l’instruction englobante try
suivante. Si une autre exception était en cours de propagation, cette exception est perdue. Le processus de propagation d’une exception est abordé plus loin dans la description de l’instruction throw
(§13.10.6).
Exemple : dans le code suivant
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }
la méthode
Method
lève une exception. La première action consiste à examiner les clauses englobantescatch
, en exécutant tous les filtres d’exception. Ensuite, lafinally
clause dansMethod
s’exécute avant le transfert de contrôle vers la clause correspondantecatch
englobante. La sortie résultante est la suivante :Filter Finally Catch
exemple de fin
Le try
bloc d’une try
instruction est accessible si l’instruction try
est accessible.
Un catch
bloc d’instruction try
est accessible si l’instruction try
est accessible.
Le finally
bloc d’une try
instruction est accessible si l’instruction try
est accessible.
Le point de terminaison d’une try
instruction est accessible si les deux éléments suivants sont vrais :
- Le point de terminaison du
try
bloc est accessible ou le point de terminaison d’au moins uncatch
bloc est accessible. - Si un
finally
bloc est présent, le point de terminaison dufinally
bloc est accessible.
13.12 Instructions cochées et non cochées
Les checked
instructions et unchecked
les instructions sont utilisées pour contrôler le contexte de vérification de dépassement de capacité pour les opérations et conversions arithmétiques de type intégral.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
L’instruction checked
entraîne l’évaluation de toutes les expressions du bloc dans un contexte vérifié et l’instruction unchecked
entraîne l’évaluation de toutes les expressions du bloc dans un contexte non vérifié.
Les checked
instructions et unchecked
les instructions sont exactement équivalentes aux opérateurs et checked
aux unchecked
opérateurs (§12.8.20), sauf qu’elles opèrent sur des blocs au lieu d’expressions.
13.13 L’instruction lock
L’instruction lock
obtient le verrou d’exclusion mutuelle pour un objet donné, exécute une instruction, puis libère le verrou.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
L’expression d’une lock
instruction doit indiquer une valeur d’un type connu pour être une référence. Aucune conversion de boxe implicite (§10.2.9) n’est jamais effectuée pour l’expression d’une lock
instruction, et il s’agit donc d’une erreur au moment de la compilation pour que l’expression désigne une valeur d’un value_type.
Instruction lock
du formulaire
lock (x)
…
où x
est une expression d’une reference_type, est précisément équivalente à :
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
sauf que x
n’est évalué qu’une seule fois.
Alors qu’un verrou d’exclusion mutuelle est conservé, le code qui s’exécute dans le même thread d’exécution peut également obtenir et libérer le verrou. Toutefois, le code en cours d’exécution dans d’autres threads est bloqué pour obtenir le verrou jusqu’à ce que le verrou soit libéré.
13.14 Instruction using
L’instruction using
obtient une ou plusieurs ressources, exécute une instruction, puis supprime la ressource.
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
Une ressource est une classe ou un struct qui implémente l’interface System.IDisposable
, qui inclut une méthode sans paramètre unique nommée Dispose
. Le code qui utilise une ressource peut appeler Dispose
pour indiquer que la ressource n’est plus nécessaire.
Si la forme de resource_acquisition est local_variable_declaration le type de l’local_variable_declarationdoit être soit un type pouvant être converti implicitement en .dynamic
System.IDisposable
Si la forme de resource_acquisition est l’expression , cette expression doit être implicitement convertible en System.IDisposable
.
Les variables locales déclarées dans un resource_acquisition sont en lecture seule et doivent inclure un initialiseur. Une erreur au moment de la compilation se produit si l’instruction incorporée tente de modifier ces variables locales (via l’affectation ou les ++
--
opérateurs), prenez l’adresse de ces variables, ou transmettez-les en tant que paramètres de référence ou de sortie.
Une using
déclaration est traduite en trois parties : acquisition, utilisation et élimination. L’utilisation de la ressource est implicitement placée dans une try
instruction qui inclut une finally
clause. Cette finally
clause supprime la ressource. Si une null
ressource est acquise, aucun appel n’est Dispose
effectué et aucune exception n’est levée. Si la ressource est de type dynamic
, elle est convertie dynamiquement par le biais d’une conversion dynamique implicite (§10.2.10) en IDisposable
acquisition afin de s’assurer que la conversion réussit avant l’utilisation et la suppression.
Instruction using
du formulaire
using (ResourceType resource = «expression» ) «statement»
correspond à l’une des trois expansions possibles. Lorsqu’il ResourceType
s’agit d’un type valeur non nullable ou d’un paramètre de type avec la contrainte de type valeur (§15.2.5), l’expansion est sémantiquement équivalente à
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
sauf que le cast de resource
to System.IDisposable
ne provoquera pas la boxe.
Sinon, quand ResourceType
c’est dynamic
, l’expansion est
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
Sinon, l’expansion est
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Dans toute extension, la resource
variable est en lecture seule dans l’instruction incorporée, et la d
variable est inaccessible et invisible à l’instruction incorporée.
Une implémentation est autorisée à implémenter une using_statement donnée différemment, par exemple pour des raisons de performances, tant que le comportement est cohérent avec l’expansion ci-dessus.
Instruction using
du formulaire :
using («expression») «statement»
a les trois mêmes expansions possibles. Dans ce casResourceType
, il s’agit implicitement du type de compilation de l’expression, s’il en a un. Sinon, l’interface IDisposable
elle-même est utilisée comme ResourceType
. La resource
variable est inaccessible et invisible à l’instruction incorporée.
Lorsqu’un resource_acquisition prend la forme d’un local_variable_declaration, il est possible d’acquérir plusieurs ressources d’un type donné. Instruction using
du formulaire
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
équivaut précisément à une séquence d’instructions imbriquées using
:
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Exemple : L’exemple ci-dessous crée un fichier nommé log.txt et écrit deux lignes de texte dans le fichier. L’exemple ouvre ensuite ce même fichier pour lire et copier les lignes de texte contenues dans la console.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }
Étant donné que les
TextWriter
classes implémententTextReader
l’interfaceIDisposable
, l’exemple peut utiliserusing
des instructions pour vous assurer que le fichier sous-jacent est correctement fermé en suivant les opérations d’écriture ou de lecture.exemple de fin
13.15 L’instruction de rendement
L’instruction yield
est utilisée dans un bloc itérateur (§13.3) pour produire une valeur à l’objet énumérateur (§15.14.5) ou objet énumérable (§15.14.6) d’un itérateur ou pour signaler la fin de l’itération.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield
est un mot clé contextuel (§6.4.4) et a une signification particulière uniquement lorsqu’il est utilisé immédiatement avant un ou return
un break
mot clé.
Il existe plusieurs restrictions sur l’emplacement où une yield
instruction peut apparaître, comme décrit dans les sections suivantes.
- Il s’agit d’une erreur au moment de la compilation pour qu’une instruction (de l’un
yield
ou l’autre formulaire) apparaisse en dehors d’un method_body, d’un operator_body ou d’un accessor_body. - Il s’agit d’une erreur au moment de la compilation pour qu’une
yield
instruction (de l’un ou l’autre formulaire) apparaisse à l’intérieur d’une fonction anonyme. - Il s’agit d’une erreur au moment de la compilation d’une
yield
instruction (de l’un ou l’autre formulaire) à afficher dans lafinally
clause d’unetry
instruction. - Il s’agit d’une erreur au moment de la compilation pour qu’une
yield return
instruction apparaisse n’importe où dans une instruction qui contient n’importetry
quelle catch_clauses.
Exemple : L’exemple suivant montre certaines utilisations valides et non valides d’instructions
yield
.delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }
exemple de fin
Une conversion implicite (§10.2) doit exister du type de l’expression dans l’instruction yield return
au type de rendement (§15.14.4) de l’itérateur.
Une yield return
instruction est exécutée comme suit :
- L’expression donnée dans l’instruction est évaluée, convertie implicitement en type de rendement et affectée à la
Current
propriété de l’objet énumérateur. - L’exécution du bloc d’itérateur est suspendue. Si l’instruction se trouve dans un ou plusieurs
yield return
blocs, les blocs associéstry
ne sontfinally
exécutés pour l’instant. - La
MoveNext
méthode de l’objet énumérateur retournetrue
à son appelant, indiquant que l’objet énumérateur a réussi à atteindre l’élément suivant.
L’appel suivant à la méthode de MoveNext
l’objet énumérateur reprend l’exécution du bloc d’itérateur à partir duquel il a été suspendu pour la dernière fois.
Une yield break
instruction est exécutée comme suit :
- Si l’instruction est placée entre un ou plusieurs
yield break
blocs avec des blocs associéstry
, lefinally
contrôle est initialement transféré vers lefinally
bloc de l’instruction la plustry
interne. Quand et si le contrôle atteint le point de terminaison d’unfinally
bloc, le contrôle est transféré vers lefinally
bloc de l’instruction englobantetry
suivante. Ce processus est répété jusqu’à ce que lesfinally
blocs de toutes les instructions englobantestry
aient été exécutés. - Le contrôle est retourné à l’appelant du bloc d’itérateur. Il s’agit de la méthode ou
MoveNext
de laDispose
méthode de l’objet énumérateur.
Étant donné qu’une yield break
instruction transfère sans condition le contrôle ailleurs, le point de terminaison d’une yield break
instruction n’est jamais accessible.
ECMA C# draft specification