Partager via


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 sa if branche. Si ce code était autorisé, la variable i serait déclarée, mais elle ne pouvait jamais être utilisée. Notez toutefois que, en plaçant ila 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 valeur false, l’appel Console.WriteLine est considéré comme inaccessible. Toutefois, si i elle est modifiée pour être une variable locale

void 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 la F 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ère Console.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’instruction if n’a pas la valeur falseconstante.

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’une break 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 (mais yield 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 catchtry_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 refvariable_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 xx 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 et goto 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’est L pas accessible. Étant donné que le point de départ de L n’est pas accessible, l’instruction qui suit le point de terminaison de L 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 de base membres d’instance à partir d’une référence implicite this , 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 une nameof() 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 et x == 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 formulaire

if (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’instruction if .
  • Si l’expression booléenne génère false et si une else 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’instruction if .
  • Si l’expression booléenne génère false et si une else partie n’est pas présente, le contrôle est transféré au point de terminaison de l’instruction if .

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 falseconstante.

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 trueconstante.

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 trueconstante.

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, , byteshortushortintuintlongulongcharboolstringou 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, uintlong, ulongcharstring, 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ême switch 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 correspondante case .
    • Sinon, si une default étiquette est présente, le contrôle est transféré vers la liste d’instructions suivant l’étiquette default .
    • Sinon, le contrôle est transféré au point de terminaison de l’instruction switch .

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 explicite goto 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: et default: 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’instruction switch 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 caseou goto default une instruction, mais toute construction qui affiche le point de terminaison de la liste d’instructions inaccessible est autorisée. Par exemple, une while instruction contrôlée par l’expression true booléenne est connue pour ne jamais atteindre son point de terminaison. De même, une instruction ou throw une return 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 type string. 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 à une case constante d’étiquette. Note de fin Lorsque le type d’administration d’une switch instruction est string ou un type valeur nullable, la valeur null est autorisée en tant que case 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’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 de commutateur est référencée par une instruction accessible goto case ou goto 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 accessible break qui quitte l’instruction switch .
  • 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’une continue instruction), le contrôle est transféré au début de l’instruction while .
  • Si l’expression booléenne génère false, le contrôle est transféré au point de terminaison de l’instruction while .

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 falseconstante.

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 accessible break qui quitte l’instruction while .
  • L’instruction while est accessible et l’expression booléenne n’a pas la valeur trueconstante.

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ère true, le contrôle est transféré au début de l’instruction do . Sinon, le contrôle est transféré au point de terminaison de l’instruction do .

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 accessible break qui quitte l’instruction do .
  • Le point de terminaison de l’instruction incorporée est accessible et l’expression booléenne n’a pas la valeur trueconstante.

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’une continue 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’instruction for .

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 valeur falseconstante.

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 accessible break qui quitte l’instruction for .
  • L’instruction for est accessible et une for_condition est présente et n’a pas la valeur trueconstante.

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 readonlyque 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, IEnumerableIEnumeratoret 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’interface IEnumerable (car System.Array implémente cette interface). Le type de collection est l’interface IEnumerable , le type d’énumérateur est l’interface IEnumerator et le type d’itération est le type d’élément du type Xde tableau.
  • Si le type X d’expression est dynamic alors une conversion implicite de l’expression vers l’interface IEnumerable (§10.2.10). Le type de collection est l’interface IEnumerable et le type d’énumérateur est l’interface IEnumerator . Si l’identificateur var est donné en tant que local_variable_type le type d’itération est dynamic, sinon il est object.
  • Sinon, déterminez si le type X a une méthode appropriée GetEnumerator :
    • Effectuez une recherche de membre sur le type X avec l’identificateur GetEnumerator 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 la GetEnumerator 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’identificateur Current 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’identificateur MoveNext 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 est E, et le type d’itération est le type de la Current propriété. La Current propriété peut inclure le ref modificateur, auquel cas l’expression retournée est une variable_reference (§9.5) qui est éventuellement en lecture seule.
  • Sinon, recherchez une interface énumérable :
    • Si, parmi tous les types Tᵢ pour lesquels il existe une conversion implicite de X vers IEnumerable<Tᵢ>, il existe un type T unique qui T n’est pas dynamic et pour tous les autres Tᵢ il y a une conversion implicite de IEnumerable<T> vers IEnumerable<Tᵢ>, alors le type de collection est l’interface IEnumerable<T>, le type d’énumérateur est l’interface IEnumerator<T>, et le type d’itération est T.
    • 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’interface System.Collections.IEnumerable , le type de collection est cette interface, le type d’énumérateur est l’interface System.Collections.IEnumeratoret le type d’itération est object.
    • Sinon, une erreur est générée et aucune autre procédure n’est effectuée.

Les étapes ci-dessus, si elles réussissent, produisent sans ambiguïté un type Cde collection, un type d’énumérateur et un type ETd’itération , ref Tou 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 valeur nullest définie, une System.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 la while boucle, il serait partagé entre toutes les itérations, et sa valeur après la for boucle serait la valeur finale, 13c’est-à-dire ce que l’appel d’imprimer f . Au lieu de cela, étant donné que chaque itération a sa propre variable v, celle capturée par f la première itération continuera à contenir la valeur 7, c’est-à-dire ce qui sera imprimé. (Notez que les versions antérieures de C# déclarées v en dehors de la while 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’interface System.IDisposable ,

    • S’il s’agit E d’un type valeur non nullable, la finally 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’une eSystem.IDisposable valeur ne provoque pas la boxing.

  • Sinon, s’il s’agit E d’un type scellé, la finally 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éduit intcomme étant , le type d’itération de numbers.

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 à deux try 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 switchla plus proche, , whiledo, forou foreach l’instruction.

break_statement
    : 'break' ';'
    ;

La cible d’une break instruction est le point de terminaison de l’instruction englobante la plus switchproche , , whiledo, forou foreach de l’instruction. Si une instruction n’est pas entourée d’une breakswitchinstruction , ou whiledoforforeachd’une instruction, une erreur au moment de la compilation se produit.

Lorsque plusieurs switchinstructions sont whiledoforforeachimbriqué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 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.
  • 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 whileproche, ou doforforeach 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 whileproche, doou forforeach de l’instruction. Si une continue instruction n’est pas entourée d’une whileinstruction , doou forforeach d’une instruction, une erreur au moment de la compilation se produit.

Lorsque plusieurs whileinstructions sont doforforeach 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 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.
  • 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 gotoinstruction 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’exemple

class 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 casetelles instructions, ou si le constant_expressionswitchcompilation 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 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.
  • 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 refest 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 voidde 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 returntry blocs avec des blocs associéscatch, le finally 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 englobantes try 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.Exceptionen , 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 instruction S, en commençant par l’instruction la plus try interne et se terminant par l’instruction la plus try externe, les étapes suivantes sont évaluées :

    • Si le try bloc d’entoure S le point de levée et s’il S comporte une ou plusieurs catch clauses, les catch clauses sont examinées afin de localiser un gestionnaire approprié pour l’exception. La première catch clause qui spécifie un type T d’exception (ou un paramètre de type qui, au moment de l’exécution, désigne un type Td’exception) de sorte que le type d’exécution de E dérives T 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 un catch filtre d’exception, cette catch clause est considérée comme une correspondance si le filtre d’exception est évalué à true. Une clause générale catch (§13.11) est considérée comme une correspondance pour tout type d’exception. Si une clause correspondante catch se trouve, la propagation de l’exception est terminée en transférant le contrôle vers le bloc de cette catch clause.
    • Sinon, si le try bloc ou un catch bloc d’entoure le point de S levée et s’il S a un finally bloc, le contrôle est transféré vers le finally bloc. Si le bloc finally lève une autre exception, le traitement de l’exception actuelle est terminé. Sinon, lorsque le contrôle atteint le point de terminaison du finally bloc, le traitement de l’exception actuelle est continué.
  • 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_filtercatchbloc, 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érale catch peut être utilisée pour intercepter ces exceptions. Par conséquent, une clause générale catch est sémantiquement différente de celle qui spécifie le type System.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ée e 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, continueou goto une instruction pour transférer le contrôle hors d’un finally bloc. Lorsqu’une breakinstruction ou une continuegoto 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 breakinstruction , continueou gotoreturn 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 englobantes catch , en exécutant tous les filtres d’exception. Ensuite, la finally clause dans Method s’exécute avant le transfert de contrôle vers la clause correspondante catch 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 un catch bloc est accessible.
  • Si un finally bloc est présent, le point de terminaison du finally 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)

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 .dynamicSystem.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émentent TextReader l’interface IDisposable , l’exemple peut utiliser using 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' ';'
    ;

yieldest 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 la finally clause d’une try 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’importe try 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és try ne sont finally exécutés pour l’instant.
  • La MoveNext méthode de l’objet énumérateur retourne true à 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, le finally 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 englobantes try 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 la Dispose 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.