Compartir a través de


13 Instrucciones

13.1 General

C# proporciona una variedad de instrucciones.

Nota: La mayoría de estas instrucciones serán familiares para los desarrolladores que han programado en C y C++. nota final

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) y fixed_statement (§23.7) solo están disponibles en código no seguro (§23).

El embedded_statement noterminal se usa para instrucciones que aparecen dentro de otras instrucciones. El uso de embedded_statement en lugar de instrucción excluye el uso de instrucciones de declaración y instrucciones etiquetadas en estos contextos.

Ejemplo: el código

void F(bool b)
{
   if (b)
      int i = 44;
}

produce un error en tiempo de compilación porque una if instrucción requiere un embedded_statement en lugar de una instrucción para su if rama. Si se permitía este código, la variable i se declararía, pero nunca se podría usar. Tenga en cuenta, sin embargo, que al colocar ila declaración de en un bloque, el ejemplo es válido.

ejemplo final

13.2 Puntos finales y accesibilidad

Cada instrucción tiene un punto de conexión. En términos intuitivos, el punto final de una instrucción es la ubicación que sigue inmediatamente a la instrucción . Las reglas de ejecución para instrucciones compuestas (instrucciones que contienen instrucciones incrustadas) especifican la acción que se realiza cuando el control alcanza el punto final de una instrucción incrustada.

Ejemplo: cuando el control alcanza el punto final de una instrucción en un bloque, el control se transfiere a la siguiente instrucción del bloque. ejemplo final

Si es posible que se pueda acceder a una instrucción mediante la ejecución, se dice que la instrucción es accesible. Por el contrario, si no existe la posibilidad de que se ejecute una instrucción, se dice que la instrucción es inaccesible.

Ejemplo: en el código siguiente

void F()
{
    Console.WriteLine("reachable");
    goto Label;
    Console.WriteLine("unreachable");
  Label:
    Console.WriteLine("reachable");
}

la segunda invocación de Console.WriteLine no es accesible porque no existe la posibilidad de que se ejecute la instrucción .

ejemplo final

Se notifica una advertencia si no se puede acceder a una instrucción distinta de throw_statement, bloque o empty_statement . En concreto, no es un error para que una instrucción sea inaccesible.

Nota: Para determinar si se puede acceder a una instrucción determinada o un punto de conexión, el compilador realiza el análisis de flujo según las reglas de accesibilidad definidas para cada instrucción. El análisis de flujo tiene en cuenta los valores de las expresiones constantes (§12.23) que controlan el comportamiento de las instrucciones, pero no se tienen en cuenta los valores posibles de expresiones no constantes. En otras palabras, con fines de análisis de flujo de control, se considera que una expresión no constante de un tipo determinado tiene cualquier valor posible de ese tipo.

En el ejemplo

void F()
{
    const int i = 1;
    if (i == 2)
        Console.WriteLine("unreachable");
}

La expresión booleana de la if instrucción es una expresión constante porque ambos operandos del == operador son constantes. A medida que la expresión constante se evalúa en tiempo de compilación, la generación del valor false, la Console.WriteLine invocación se considera inaccesible. Sin embargo, si i se cambia para que sea una variable local

void F()
{
    int i = 1;
    if (i == 2)
        Console.WriteLine("reachable");
}

la Console.WriteLine invocación se considera accesible, aunque, en realidad, nunca se ejecutará.

nota final

El bloque de un miembro de función o una función anónima siempre se considera accesible. Al evaluar sucesivamente las reglas de accesibilidad de cada instrucción en un bloque, se puede determinar la capacidad de acceso de cualquier instrucción determinada.

Ejemplo: en el código siguiente

void F(int x)
{
    Console.WriteLine("start");
    if (x < 0)
        Console.WriteLine("negative");
}

la capacidad de acceso del segundo Console.WriteLine se determina de la siguiente manera:

  • La primera Console.WriteLine instrucción de expresión es accesible porque el bloque del F método es accesible (§13.3).
  • El punto final de la primera Console.WriteLine instrucción de expresión es accesible porque esa instrucción es accesible (§13.7 y §13.3).
  • La if instrucción es accesible porque el punto final de la primera Console.WriteLine instrucción de expresión es accesible (§13.7 y §13.3).
  • La segunda Console.WriteLine instrucción de expresión es accesible porque la expresión booleana de la if instrucción no tiene el valor falseconstante .

ejemplo final

Hay dos situaciones en las que se trata de un error en tiempo de compilación para que el punto final de una instrucción sea accesible:

  • Dado que la switch instrucción no permite que una sección de modificador "pase" a la siguiente sección del modificador, se trata de un error en tiempo de compilación para que el punto final de la lista de instrucciones de una sección de modificador sea accesible. Si se produce este error, suele ser una indicación de que falta una break instrucción.

  • Es un error en tiempo de compilación para el punto final del bloque de un miembro de función o una función anónima que calcula un valor al que se puede acceder. Si se produce este error, normalmente es una indicación de que falta una return instrucción (§13.10.5).

13.3 Bloques

13.3.1 General

Un bloque permite que se escriban varias instrucciones en contextos donde se permite una única instrucción.

block
    : '{' statement_list? '}'
    ;

Un bloque consta de una statement_list opcional (§13.3.2), entre llaves. Si se omite la lista de instrucciones, se dice que el bloque está vacío.

Un bloque puede contener instrucciones de declaración (§13.6). El ámbito de una variable local o constante declarada en un bloque es el bloque .

Se ejecuta un bloque como se indica a continuación:

  • Si el bloque está vacío, el control se transfiere al punto final del bloque.
  • Si el bloque no está vacío, el control se transfiere a la lista de instrucciones. Cuando y si el control llega al punto final de la lista de instrucciones, el control se transfiere al punto final del bloque.

La lista de instrucciones de un bloque es accesible si se puede acceder al propio bloque.

El punto final de un bloque es accesible si el bloque está vacío o si se puede acceder al punto final de la lista de instrucciones.

Un bloque que contiene una o varias yield instrucciones (§13.15) se denomina bloque de iterador. Los bloques de iterador se usan para implementar miembros de función como iteradores (§15.14). Algunas restricciones adicionales se aplican a los bloques de iterador:

  • Es un error en tiempo de compilación para que una return instrucción aparezca en un bloque de iterador (pero yield return se permiten instrucciones).
  • Es un error en tiempo de compilación para que un bloque de iterador contenga un contexto no seguro (§23.2). Un bloque de iterador siempre define un contexto seguro, incluso cuando su declaración está anidada en un contexto no seguro.

13.3.2 Listas de instrucciones

Una lista de instrucciones consta de una o varias instrucciones escritas en secuencia. Las listas de instrucciones se producen en los bloquess (§13.3) y en switch_blocks (§13.8.3).

statement_list
    : statement+
    ;

Una lista de instrucciones se ejecuta transfiriendo el control a la primera instrucción. Cuando y si el control llega al punto final de una instrucción, el control se transfiere a la instrucción siguiente. Cuando y si el control llega al punto final de la última instrucción, el control se transfiere al punto final de la lista de instrucciones.

Se puede acceder a una instrucción de una lista de instrucciones si se cumple al menos una de las siguientes condiciones:

  • La instrucción es la primera instrucción y la propia lista de instrucciones es accesible.
  • Se puede acceder al punto final de la instrucción anterior.
  • La instrucción es una instrucción etiquetada y una instrucción accesible goto hace referencia a la etiqueta.

El punto final de una lista de instrucciones es accesible si se puede acceder al punto final de la última instrucción de la lista.

13.4 Instrucción vacía

Un empty_statement no hace nada.

empty_statement
    : ';'
    ;

Se usa una instrucción vacía cuando no hay ninguna operación para realizar en un contexto en el que se requiere una instrucción.

La ejecución de una instrucción vacía simplemente transfiere el control al punto final de la instrucción. Por lo tanto, se puede acceder al punto final de una instrucción vacía si se puede acceder a la instrucción vacía.

Ejemplo: se puede usar una instrucción vacía al escribir una while instrucción con un cuerpo NULL:

bool ProcessMessage() {...}
void ProcessMessages()
{
    while (ProcessMessage())
        ;
}

Además, se puede usar una instrucción vacía para declarar una etiqueta justo antes del cierre "}" de un bloque:

void F(bool done)
{
    ...
    if (done)
    {
        goto exit;
    }
    ...
  exit:
    ;
}

ejemplo final

13.5 Instrucciones etiquetadas

Un labeled_statement permite que una instrucción tenga como prefijo una etiqueta. Las instrucciones etiquetadas se permiten en bloques, pero no se permiten como instrucciones insertadas.

labeled_statement
    : identifier ':' statement
    ;

Una instrucción etiquetada declara una etiqueta con el nombre proporcionado por el identificador. El ámbito de una etiqueta es el bloque completo en el que se declara la etiqueta, incluidos los bloques anidados. Se trata de un error en tiempo de compilación para que dos etiquetas con el mismo nombre tengan ámbitos superpuestos.

Se puede hacer referencia a una etiqueta desde goto instrucciones (§13.10.4) dentro del ámbito de la etiqueta.

Nota: Esto significa que goto las instrucciones pueden transferir el control dentro de bloques y fuera de bloques, pero nunca en bloques. nota final

Las etiquetas tienen su propio espacio de declaración y no interfieren con otros identificadores.

Ejemplo: El ejemplo

int F(int x)
{
    if (x >= 0)
    {
        goto x;
    }
    x = -x;
  x:
    return x;
}

es válido y usa el nombre x como un parámetro y una etiqueta.

ejemplo final

La ejecución de una instrucción etiquetada corresponde exactamente a la ejecución de la instrucción que sigue a la etiqueta.

Además de la accesibilidad proporcionada por el flujo normal de control, se puede acceder a una instrucción etiquetada si se hace referencia a la etiqueta mediante una goto instrucción accesible, a menos que la goto instrucción esté dentro del try bloque o un catch bloque de un try_statement que incluya un finally bloque cuyo punto de conexión sea inaccesible y la instrucción etiquetada esté fuera del try_statement.

13.6 Declaraciones

13.6.1 General

Un declaration_statement declara una o varias variables locales, una o varias constantes locales o una función local. Las instrucciones de declaración se permiten en bloques y bloques switch, pero no se permiten como instrucciones insertadas.

declaration_statement
    : local_variable_declaration ';'
    | local_constant_declaration ';'
    | local_function_declaration
    ;

Una variable local se declara mediante un local_variable_declaration (§13.6.2). Una constante local se declara mediante un local_constant_declaration (§13.6.3). Una función local se declara mediante un local_function_declaration (§13.6.4).

Los nombres declarados se introducen en el espacio de declaración envolvente más cercano (§7.3).

13.6.2 Declaraciones de variables locales

13.6.2.1 General

Un local_variable_declaration declara una o varias variables locales.

local_variable_declaration
    : implicitly_typed_local_variable_declaration
    | explicitly_typed_local_variable_declaration
    | explicitly_typed_ref_local_variable_declaration
    ;

Las declaraciones con tipo implícito contienen la palabra clave contextual (§6.4.4) var que da lugar a una ambigüedad sintáctica entre las tres categorías que se resuelven de la siguiente manera:

  • Si no hay ningún tipo denominado var en el ámbito y la entrada coincide con implicitly_typed_local_variable_declaration , se elige;
  • De lo contrario, si un tipo denominado var está en el ámbito, implicitly_typed_local_variable_declaration no se considera una coincidencia posible.

Dentro de un local_variable_declaration cada variable se introduce mediante un declarador, que es uno de implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator o ref_local_variable_declarator para variables locales con tipo implícito, explícitamente tipadas y ref, respectivamente. El declarador define el nombre (identificador) y el valor inicial, si existe, de la variable introducida.

Si hay varios declaradores en una declaración, se procesan, incluidas las expresiones de inicialización, en orden de izquierda a derecha (§9.4.4.5).

Nota: Para una local_variable_declaration no se produce como una for_initializer (§13.9.4) o resource_acquisition (§13.14), este orden de izquierda a derecha es equivalente a que cada declarador esté dentro de un local_variable_declaration independiente. Por ejemplo:

void F()
{
    int x = 1, y, z = x * 2;
}

equivale a:

void F()
{
    int x = 1;
    int y;
    int z = x * 2;
}

nota final

El valor de una variable local se obtiene en una expresión mediante un simple_name (§12.8.4). Se asignará definitivamente una variable local (§9.4) en cada ubicación donde se obtenga su valor. Cada variable local introducida por un local_variable_declaration está inicialmente sin asignar (§9.4.3). Si un declarador tiene una expresión de inicialización, la variable local introducida se clasifica como asignada al final del declarador (§9.4.4.5).

El ámbito de una variable local introducida por un local_variable_declaration se define de la siguiente manera (§7.7):

  • Si la declaración se produce como un for_initializer , el ámbito es el for_initializer, for_condition, for_iterator y embedded_statement (§13.9.4);
  • Si la declaración se produce como un resource_acquisition , el ámbito es el bloque más externo de la expansión semánticamente equivalente de la using_statement (§13.14);
  • De lo contrario, el ámbito es el bloque en el que se produce la declaración.

Es un error hacer referencia a una variable local por nombre en una posición textual que precede a su declarador o dentro de cualquier expresión inicializador dentro de su declarador. Dentro del ámbito de una variable local, se trata de un error en tiempo de compilación para declarar otra variable local, función local o constante con el mismo nombre.

El contexto ref-safe-context (§9.7.2) de una variable local ref es el contexto ref-safe-context de su inicialización variable_reference. El contexto ref-safe-of non-ref local variables es declaration-block.

13.6.2.2 Declaraciones de variables locales con tipo implícito

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
    ;

Un implicitly_typed_local_variable_declaration introduce una única variable local, un identificador. La expresión o variable_reference tendrá un tipo en tiempo de compilación, T. La primera alternativa declara una variable con un valor inicial de expresión; su tipo es T? cuando T es un tipo de referencia que no acepta valores NULL; de lo contrario, su tipo es T. La segunda alternativa declara una variable ref con un valor inicial de variable_reference; su tipo es ref T? cuando T es un tipo de ref referencia que no acepta valores NULL; de lo contrario, su tipo es ref T. (ref_kind se describe en §15.6.1).

Ejemplo:

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;

Las declaraciones de variables locales con tipo implícito anteriores son exactamente equivalentes a las siguientes declaraciones con tipo explícito:

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;

Las siguientes son declaraciones de variables locales con tipo implícito incorrectas:

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

ejemplo final

13.6.2.3 Declaraciones de variables locales con tipo explícito

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
    ;

Un explicity_typed_local_variable_declaration introduce una o varias variables locales con el tipo especificado.

Si existe un local_variable_initializer , su tipo será adecuado según las reglas de asignación simple (§12.21.2) o inicialización de matriz (§17.7) y su valor se asigna como valor inicial de la variable.

13.6.2.4 Declaraciones de variables locales ref tipadas explícitamente

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
    ;

La inicialización variable_reference tendrá un tipo de tipo y cumplirá los mismos requisitos que para una asignación de referencia (§12.21.3).

Si ref_kind es ref readonly, los identificadores que se declaran son referencias a variables que se tratan como de solo lectura. De lo contrario, si ref_kind es ref, los identificadores que se declaran son referencias a variables que se pueden escribir.

Se trata de un error en tiempo de compilación para declarar una variable local ref o una variable de un tipo, dentro de un ref struct método declarado con el method_modifier async , o dentro de un iterador (§15.14).

13.6.3 Declaraciones de constante local

Un local_constant_declaration declara una o varias constantes locales.

local_constant_declaration
    : 'const' type constant_declarators
    ;

constant_declarators
    : constant_declarator (',' constant_declarator)*
    ;

constant_declarator
    : identifier '=' constant_expression
    ;

El tipo de un local_constant_declaration especifica el tipo de las constantes introducidas por la declaración. El tipo va seguido de una lista de constant_declarators, cada una de las cuales presenta una nueva constante. Un constant_declarator consta de un identificador que denomina la constante, seguido de un token "=", seguido de un constant_expression (§12.23) que proporciona el valor de la constante.

El tipo y constant_expression de una declaración constante local seguirán las mismas reglas que las de una declaración de miembro constante (§15.4).

El valor de una constante local se obtiene en una expresión mediante un simple_name (§12.8.4).

El ámbito de una constante local es el bloque en el que se produce la declaración. Es un error hacer referencia a una constante local en una posición textual que precede al final de su constant_declarator.

Una declaración de constante local que declara varias constantes es equivalente a varias declaraciones de constantes únicas con el mismo tipo.

13.6.4 Declaraciones de función local

Un local_function_declaration declara una función local.

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 ';'
    ;

Nota gramatical: Al reconocer un local_function_body si se aplican tanto el null_conditional_invocation_expression como las alternativas de expresión , se elegirá la primera. (§15.6.1)

Ejemplo: hay dos casos de uso comunes para las funciones locales: métodos de iterador y métodos asincrónicos. En los métodos de iterador, las excepciones solo se observan al llamar a código que enumera la secuencia devuelta. En los métodos asincrónicos, las excepciones solo se observan cuando se espera la tarea devuelta. En el ejemplo siguiente se muestra la separación de la validación de parámetros de la implementación de iteradores mediante una función local:

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;
        }
    }
}

ejemplo final

A menos que se especifique lo contrario, la semántica de todos los elementos gramaticales es la misma que para method_declaration (§15.6.1), lea en el contexto de una función local en lugar de un método.

El identificador de un local_function_declaration será único en su ámbito de bloque declarado, incluidos los espacios de declaración de variables locales envolventes. Una consecuencia de esto es que no se permiten los local_function_declarationsobrecargados.

Un local_function_declaration puede incluir un async modificador (§15.15) y un unsafe modificador (§23.1). Si la declaración incluye el async modificador, el tipo de valor devuelto será void o un «TaskType» tipo (§15.15.1). Si la declaración incluye el static modificador, la función es una función local estática; de lo contrario, es una función local no estática. Se trata de un error en tiempo de compilación para que type_parameter_list o parameter_list contengan atributos. Si la función local se declara en un contexto no seguro (§23.2), la función local puede incluir código no seguro, incluso si la declaración de función local no incluye el unsafe modificador.

Una función local se declara en el ámbito de bloque. Una función local no estática puede capturar variables del ámbito envolvente, mientras que una función local estática no debe (por lo que no tiene acceso a variables locales, parámetros, funciones locales no estáticas o this). Es un error en tiempo de compilación si el cuerpo de una función local no estática lee una variable capturada, pero no se asigna definitivamente antes de cada llamada a la función. El compilador determinará qué variables se asignan definitivamente al devolver (§9.4.4.33).

Cuando el tipo de this es un tipo de estructura, es un error en tiempo de compilación para que el cuerpo de una función local acceda thisa . Esto es cierto si el acceso es explícito (como en this.x) o implícito (como en donde x x es un miembro de instancia de la estructura). Esta regla solo prohíbe este acceso y no afecta a si la búsqueda de miembros da como resultado un miembro de la estructura.

Es un error en tiempo de compilación para que el cuerpo de la función local contenga una goto instrucción, una break instrucción o una continue instrucción cuyo destino está fuera del cuerpo de la función local.

Nota: las reglas anteriores para this y goto reflejan las reglas para las funciones anónimas en §12.19.3. nota final

Se puede llamar a una función local desde un punto léxico antes de su declaración. Sin embargo, es un error en tiempo de compilación para que la función se declare léxicamente antes de la declaración de una variable usada en la función local (§7.7).

Es un error en tiempo de compilación para que una función local declare un parámetro, un parámetro de tipo o una variable local con el mismo nombre que uno declarado en cualquier espacio de declaración de variable local envolvente.

Los cuerpos de función locales siempre son accesibles. El punto de conexión de una declaración de función local es accesible si se puede acceder al punto inicial de la declaración de función local.

Ejemplo: En el ejemplo siguiente, el cuerpo de L es accesible aunque el punto inicial de L no sea accesible. Dado que el punto inicial de L no es accesible, la instrucción que sigue al punto de conexión de L no es accesible:

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 otras palabras, la ubicación de una declaración de función local no afecta a la accesibilidad de las instrucciones de la función contenedora. ejemplo final

Si el tipo del argumento para una función local es dynamic, la función a la que se va a llamar se resolverá en tiempo de compilación, no en tiempo de ejecución.

Una función local no se usará en un árbol de expresión.

Una función local estática

  • Puede hacer referencia a miembros estáticos, parámetros de tipo, definiciones constantes y funciones locales estáticas desde el ámbito envolvente.
  • No hará referencia a this ni base a los miembros de instancia de una referencia implícita this , ni variables locales, parámetros ni funciones locales no estáticas desde el ámbito envolvente. Sin embargo, todas estas se permiten en una nameof() expresión.

13.7 Instrucciones expression

Un expression_statement evalúa una expresión determinada. El valor calculado por la expresión, si existe, se descarta.

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
    ;

No todas las expresiones se permiten como instrucciones.

Nota: En concreto, las expresiones como x + y y x == 1, que simplemente calculan un valor (que se descartará), no se permiten como instrucciones. nota final

La ejecución de un expression_statement evalúa la expresión contenida y, a continuación, transfiere el control al punto final del expression_statement. El punto final de un expression_statement es accesible si se puede acceder a ese expression_statement .

13.8 Instrucciones selection

13.8.1 General

Las instrucciones de selección seleccionan una de las posibles instrucciones para su ejecución en función del valor de alguna expresión.

selection_statement
    : if_statement
    | switch_statement
    ;

13.8.2 Instrucción if

La if instrucción selecciona una instrucción para su ejecución en función del valor de una expresión booleana.

if_statement
    : 'if' '(' boolean_expression ')' embedded_statement
    | 'if' '(' boolean_expression ')' embedded_statement
      'else' embedded_statement
    ;

Una else parte está asociada con el elemento léxico más cercano anterior if permitido por la sintaxis.

Ejemplo: Por lo tanto, una if instrucción del formulario

if (x) if (y) F(); else G();

es equivalente a

if (x)
{
    if (y)
    {
        F();
    }
    else
    {
        G();
    }
}

ejemplo final

Se ejecuta una if instrucción como se indica a continuación:

  • Se evalúa el boolean_expression (§12.24).
  • Si la expresión booleana produce true, el control se transfiere a la primera instrucción insertada. Cuando y si el control llega al punto final de esa instrucción, el control se transfiere al punto final de la if instrucción.
  • Si la expresión booleana produce false y si hay una else parte presente, el control se transfiere a la segunda instrucción insertada. Cuando y si el control llega al punto final de esa instrucción, el control se transfiere al punto final de la if instrucción.
  • Si la expresión booleana produce false y si una else parte no está presente, el control se transfiere al punto final de la if instrucción.

La primera instrucción insertada de una if instrucción es accesible si la if instrucción es accesible y la expresión booleana no tiene el valor falseconstante .

La segunda instrucción insertada de una if instrucción, si está presente, es accesible si la if instrucción es accesible y la expresión booleana no tiene el valor trueconstante .

El punto final de una if instrucción es accesible si se puede acceder al punto final de al menos una de sus instrucciones incrustadas. Además, el punto final de una if instrucción sin else elemento es accesible si la if instrucción es accesible y la expresión booleana no tiene el valor trueconstante .

13.8.3 Instrucción switch

La switch instrucción selecciona para ejecutar una lista de instrucciones que tiene una etiqueta de modificador asociada que corresponde al valor de la expresión switch.

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 consta de la palabra clave switch, seguido de una expresión entre paréntesis (denominada expresión switch), seguida de un switch_block. El switch_block consta de cero o más switch_sections, entre llaves. Cada switch_section consta de una o varias switch_labelseguidas de un statement_list (§13.3.2). Cada switch_label que contiene case tiene un patrón asociado (§11) con el que se prueba el valor de la expresión switch. Si case_guard está presente, su expresión se podrá convertir implícitamente en el tipo bool y esa expresión se evaluará como una condición adicional para que el caso se considere satisfecho.

La expresión switch establece el tipo de gobernanza de una switch instrucción.

  • Si el tipo de la expresión switch es sbyte, byte, shortuintintulongboollongushortchar, stringo un enum_type, o si es el tipo de valor que acepta valores NULL correspondiente a uno de estos tipos, ese es el tipo de gobierno de la switch instrucción.
  • De lo contrario, si existe exactamente una conversión implícita definida por el usuario del tipo de la expresión switch a uno de los siguientes tipos de gobierno posibles: sbyte, byte, ushortlongintshortulongcharuintostring, un tipo de valor que acepta valores NULL correspondiente a uno de esos tipos, el tipo convertido es el tipo de gobierno de la switch instrucción.
  • De lo contrario, el tipo de gobernanza de la switch instrucción es el tipo de la expresión switch. Se trata de un error si no existe este tipo.

Puede haber como máximo una default etiqueta en una switch instrucción .

Se trata de un error si el patrón de cualquier etiqueta de modificador no es aplicable (§11.2.1) al tipo de la expresión de entrada.

Se trata de un error si el patrón de cualquier etiqueta de modificador está subsumado por (§11.3) el conjunto de patrones de etiquetas de conmutador anteriores de la instrucción switch que no tienen una protección de mayúsculas y minúsculas o cuya protección de mayúsculas y minúsculas es una expresión constante con el valor true.

Ejemplo:

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

ejemplo final

Se ejecuta una switch instrucción como se indica a continuación:

  • La expresión switch se evalúa y convierte en el tipo de gobierno.
  • El control se transfiere según el valor de la expresión switch convertida:
    • El primer patrón léxico del conjunto de etiquetas de case la misma switch instrucción que coincide con el valor de la expresión switch y para el que la expresión de protección está ausente o se evalúa como true, hace que el control se transfiera a la lista de instrucciones después de la etiqueta coincidente case .
    • De lo contrario, si hay una default etiqueta presente, el control se transfiere a la lista de instrucciones después de la default etiqueta.
    • De lo contrario, el control se transfiere al punto final de la switch instrucción .

Nota: No se define el orden en el que se coinciden los patrones en tiempo de ejecución. Se permite que un compilador (pero no necesario) coincida con patrones desordenados y reutilizar los resultados de patrones ya coincidentes para calcular el resultado de la coincidencia de otros patrones. Sin embargo, el compilador es necesario para determinar el primer patrón léxico que coincide con la expresión y para la que la cláusula guard está ausente o se evalúa como true. nota final

Si se puede acceder al punto final de la lista de instrucciones de una sección switch, se produce un error en tiempo de compilación. Esto se conoce como regla de "no caer a través".

Ejemplo: El ejemplo

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    default:
        CaseOthers();
        break;
}

es válido porque ninguna sección de conmutador tiene un punto de conexión accesible. A diferencia de C y C++, la ejecución de una sección switch no puede "pasarse" a la siguiente sección del modificador y el ejemplo

switch (i)
{
    case 0:
        CaseZero();
    case 1:
        CaseZeroOrOne();
    default:
        CaseAny();
}

produce un error en tiempo de compilación. Cuando la ejecución de una sección switch va a ir seguida de la ejecución de otra sección switch, se usará una instrucción o goto default explícitagoto case:

switch (i)
{
    case 0:
        CaseZero();
        goto case 1;
    case 1:
        CaseZeroOrOne();
        goto default;
    default:
        CaseAny();
        break;
}

ejemplo final

Se permiten varias etiquetas en una switch_section.

Ejemplo: El ejemplo

switch (i)
{
    case 0:
        CaseZero();
        break;
    case 1:
        CaseOne();
        break;
    case 2:
    default:
        CaseTwo();
        break;
}

es válido. El ejemplo no infringe la regla "no fall through" porque las etiquetas case 2: y default: forman parte del mismo switch_section.

ejemplo final

Nota: La regla "sin caída" impide una clase común de errores que se producen en C y C++ cuando break las instrucciones se omiten accidentalmente. Por ejemplo, las secciones de la switch instrucción anterior se pueden invertir sin afectar al comportamiento de la instrucción :

switch (i)
{
    default:
        CaseAny();
        break;
    case 1:
        CaseZeroOrOne();
        goto default;
    case 0:
        CaseZero();
        goto case 1;
}

nota final

Nota: La lista de instrucciones de una sección switch normalmente termina en una breakinstrucción , goto caseo goto default , pero se permite cualquier construcción que represente el punto final de la lista de instrucciones inaccesible. Por ejemplo, se sabe que una while instrucción controlada por la expresión true booleana nunca alcanza su punto final. Del mismo modo, una throw instrucción o return siempre transfiere el control en otro lugar y nunca alcanza su punto de conexión. Por lo tanto, el ejemplo siguiente es válido:

switch (i)
{
     case 0:
         while (true)
         {
             F();
         }
     case 1:
         throw new ArgumentException();
     case 2:
         return;
}

nota final

Ejemplo: el tipo de gobernanza de una switch instrucción puede ser el tipo string. Por ejemplo:

void DoCommand(string command)
{
    switch (command.ToLower())
    {
        case "run":
            DoRun();
            break;
        case "save":
            DoSave();
            break;
        case "quit":
            DoQuit();
            break;
        default:
            InvalidCommand(command);
            break;
    }
}

ejemplo final

Nota: Al igual que los operadores de igualdad de cadenas (§12.12.8), la switch instrucción distingue mayúsculas de minúsculas y ejecutará una sección de conmutador determinada solo si la cadena de expresión switch coincide exactamente con una case constante de etiqueta. nota final Cuando el tipo de gobernanza de una switch instrucción es string o un tipo de valor que acepta valores NULL, el valor null se permite como una case constante de etiqueta.

Los statement_listde un switch_block pueden contener instrucciones de declaración (§13.6). El ámbito de una variable local o constante declarada en un bloque switch es el bloque switch.

Se puede acceder a una etiqueta switch si se cumple al menos una de las siguientes opciones:

  • La expresión switch es un valor constante y cualquiera de los dos
    • la etiqueta es un case cuyo patrón coincidiría (§11.2.1) ese valor y la protección de la etiqueta está ausente o no una expresión constante con el valor false; o bien
    • es una default etiqueta y ninguna sección switch contiene una etiqueta de mayúsculas y minúsculas cuyo patrón coincidiría con ese valor y cuya protección está ausente o una expresión constante con el valor true.
  • La expresión switch no es un valor constante y tampoco
    • la etiqueta es sin case protección o con una protección cuyo valor no es la constante false; o
    • es una default etiqueta y
      • el conjunto de patrones que aparecen entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true, no es exhaustivo (§11.4) para el tipo de regulación del modificador; o
      • el tipo de control switch es un tipo que acepta valores NULL y el conjunto de patrones que aparecen entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true no contiene un patrón que coincida con el valor null.
  • Se hace referencia a la etiqueta del modificador mediante una instrucción o goto default accesiblegoto case.

La lista de instrucciones de una sección de conmutador determinada es accesible si la switch instrucción es accesible y la sección switch contiene una etiqueta de conmutador accesible.

El punto final de una switch instrucción es accesible si se puede acceder a la instrucción switch y al menos se cumple una de las siguientes condiciones:

  • La switch instrucción contiene una instrucción accesible break que sale de la switch instrucción .
  • Ninguna default etiqueta está presente y tampoco
    • La expresión switch es un valor no constante y el conjunto de patrones que aparecen entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true, no es exhaustivo (§11.4) para el tipo de regulación del conmutador.
    • La expresión switch es un valor no constante de un tipo que acepta valores NULL y ningún patrón que aparece entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true coincidiría con el valor null.
    • La expresión switch es un valor constante y ninguna case etiqueta sin una protección o cuya protección es la constante true coincidiría con ese valor.

Ejemplo: El código siguiente muestra un uso concisa de la when cláusula :

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";
    }
}

El caso var coincide con null, la cadena vacía o cualquier cadena que contenga solo espacio en blanco. ejemplo final

13.9 Instrucciones de iteración

13.9.1 General

Las instrucciones de iteración ejecutan repetidamente una instrucción insertada.

iteration_statement
    : while_statement
    | do_statement
    | for_statement
    | foreach_statement
    ;

13.9.2 Instrucción while

La while instrucción ejecuta condicionalmente una instrucción incrustada cero o más veces.

while_statement
    : 'while' '(' boolean_expression ')' embedded_statement
    ;

Se ejecuta una while instrucción como se indica a continuación:

  • Se evalúa el boolean_expression (§12.24).
  • Si la expresión booleana produce true, el control se transfiere a la instrucción insertada. Cuando y si el control llega al punto final de la instrucción incrustada (posiblemente desde la ejecución de una continue instrucción), el control se transfiere al principio de la while instrucción.
  • Si la expresión booleana produce false, el control se transfiere al punto final de la while instrucción .

Dentro de la instrucción insertada de una while instrucción, se puede usar una break instrucción (§13.10.2) para transferir el control al punto final de la while instrucción (por lo tanto, la iteración final de la instrucción incrustada) y una continue instrucción (§13.10.3) se puede usar para transferir el control al punto final de la instrucción incrustada (realizando así otra iteración de la while instrucción).

La instrucción insertada de una while instrucción es accesible si la while instrucción es accesible y la expresión booleana no tiene el valor falseconstante .

El punto final de una while instrucción es accesible si se cumple al menos uno de los siguientes elementos:

  • La while instrucción contiene una instrucción accesible break que sale de la while instrucción .
  • La while instrucción es accesible y la expresión booleana no tiene el valor trueconstante .

13.9.3 Instrucción do

La do instrucción ejecuta condicionalmente una instrucción incrustada una o varias veces.

do_statement
    : 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
    ;

Se ejecuta una do instrucción como se indica a continuación:

  • El control se transfiere a la instrucción insertada.
  • Cuando y si el control llega al punto final de la instrucción insertada (posiblemente desde la ejecución de una continue instrucción), se evalúa el boolean_expression (§12.24). Si la expresión booleana produce true, el control se transfiere al principio de la do instrucción . De lo contrario, el control se transfiere al punto final de la do instrucción .

Dentro de la instrucción insertada de una do instrucción, se puede usar una break instrucción (§13.10.2) para transferir el control al punto final de la do instrucción (por lo tanto, la iteración final de la instrucción incrustada) y una continue instrucción (§13.10.3) se puede usar para transferir el control al punto final de la instrucción incrustada (realizando así otra iteración de la do instrucción).

La instrucción insertada de una do instrucción es accesible si la do instrucción es accesible.

El punto final de una do instrucción es accesible si se cumple al menos uno de los siguientes elementos:

  • La do instrucción contiene una instrucción accesible break que sale de la do instrucción .
  • El punto final de la instrucción insertada es accesible y la expresión booleana no tiene el valor trueconstante .

13.9.4 Instrucción for

La for instrucción evalúa una secuencia de expresiones de inicialización y, a continuación, mientras que una condición es true, ejecuta repetidamente una instrucción incrustada y evalúa una secuencia de expresiones de iteración.

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)*
    ;

El for_initializer, si está presente, consta de un local_variable_declaration (§13.6.2) o una lista de statement_expressions (§13.7) separados por comas. El ámbito de una variable local declarada por un for_initializer es el for_initializer, for_condition, for_iterator y embedded_statement.

El for_condition, si está presente, será un boolean_expression (§12.24).

El for_iterator, si está presente, consta de una lista de statement_expressions (§13.7) separados por comas.

Se ejecuta una for instrucción como se indica a continuación:

  • Si hay un for_initializer presente, los inicializadores de variable o las expresiones de instrucción se ejecutan en el orden en que se escriben. Este paso solo se realiza una vez.
  • Si hay un for_condition presente, se evalúa.
  • Si el for_condition no está presente o si la evaluación produce true, el control se transfiere a la instrucción insertada. Cuando y si el control llega al punto final de la instrucción insertada (posiblemente desde la ejecución de una continue instrucción), las expresiones de la for_iterator, si las hay, se evalúan en secuencia y, a continuación, se realiza otra iteración, empezando por la evaluación del for_condition en el paso anterior.
  • Si el for_condition está presente y la evaluación produce false, el control se transfiere al punto final de la for instrucción .

Dentro de la instrucción insertada de una for instrucción, se puede usar una break instrucción (§13.10.2) para transferir el control al punto final de la for instrucción (por lo tanto, la iteración final de la instrucción insertada) y una continue instrucción (§13.10.3) se puede usar para transferir el control al punto final de la instrucción incrustada (ejecutando así el for_iterator y realizando otra iteración de la for instrucción, a partir del for_condition).

La instrucción insertada de una for instrucción es accesible si se cumple una de las siguientes condiciones:

  • La for instrucción es accesible y no hay ningún for_condition presente.
  • La for instrucción es accesible y hay una for_condition presente y no tiene el valor falseconstante .

El punto final de una for instrucción es accesible si se cumple al menos uno de los siguientes elementos:

  • La for instrucción contiene una instrucción accesible break que sale de la for instrucción .
  • La for instrucción es accesible y hay una for_condition presente y no tiene el valor trueconstante .

13.9.5 Instrucción foreach

La foreach instrucción enumera los elementos de una colección, ejecutando una instrucción insertada para cada elemento de la colección.

foreach_statement
    : 'foreach' '(' ref_kind? local_variable_type identifier 'in' 
      expression ')' embedded_statement
    ;

El local_variable_type e identificador de una instrucción foreach declaran la variable de iteración de la instrucción . Si el var identificador se proporciona como el local_variable_type y ningún tipo denominado var está en el ámbito, se dice que la variable de iteración es una variable de iteración con tipo implícito y su tipo se toma para ser el tipo de elemento de la foreach instrucción, como se especifica a continuación.

Si el foreach_statement contiene o ninguno ref de ellos y readonly, la variable de iteración denota una variable que se trata como de solo lectura. De lo contrario, si foreach_statement contiene ref sin readonly, la variable de iteración denota una variable que se puede escribir.

La variable de iteración corresponde a una variable local con un ámbito que se extiende a través de la instrucción insertada. Durante la ejecución de una foreach instrucción, la variable de iteración representa el elemento de colección para el que se está realizando actualmente una iteración. Si la variable de iteración denota una variable de solo lectura, se produce un error en tiempo de compilación si la instrucción insertada intenta modificarla (a través de la asignación o los ++ operadores y -- ) o pasarla como un parámetro de referencia o salida.

En lo siguiente, para mayor brevedad, IEnumerable, IEnumeratorIEnumerable<T> y IEnumerator<T> haga referencia a los tipos correspondientes System.Collections en los espacios de nombres y System.Collections.Generic.

El procesamiento en tiempo de compilación de una foreach instrucción determina primero el tipo de colección, el tipo de enumerador y el tipo de iteración de la expresión. Esta determinación continúa de la siguiente manera:

  • Si el tipo X de expresión es un tipo de matriz, hay una conversión de referencia implícita de X a la IEnumerable interfaz (ya que System.Array implementa esta interfaz). El tipo de colección es la IEnumerable interfaz, el tipo de enumerador es la IEnumerator interfaz y el tipo de iteración es el tipo de elemento del tipo Xde matriz .
  • Si el tipo X de expresión es dynamic entonces hay una conversión implícita de expresión a la IEnumerable interfaz (§10.2.10). El tipo de colección es la IEnumerable interfaz y el tipo de enumerador es la IEnumerator interfaz. Si el var identificador se proporciona como el local_variable_type , el tipo de iteración es dynamic, de lo contrario, es object.
  • De lo contrario, determine si el tipo X tiene un método adecuado GetEnumerator :
    • Realice la búsqueda de miembros en el tipo X con identificador GetEnumerator y sin argumentos de tipo. Si la búsqueda de miembros no produce una coincidencia, o genera una ambigüedad, o genera una coincidencia que no es un grupo de métodos, compruebe si hay una interfaz enumerable como se describe a continuación. Se recomienda emitir una advertencia si la búsqueda de miembros genera algo excepto un grupo de métodos o ninguna coincidencia.
    • Realice la resolución de sobrecargas mediante el grupo de métodos resultante y una lista de argumentos vacía. Si la resolución de sobrecargas no da lugar a ningún método aplicable, da como resultado una ambigüedad o da como resultado un único método mejor, pero ese método es estático o no público, compruebe si hay una interfaz enumerable como se describe a continuación. Se recomienda emitir una advertencia si la resolución de sobrecarga genera algo excepto un método de instancia pública inequívoca o ningún método aplicable.
    • Si el tipo E de valor devuelto del GetEnumerator método no es una clase, estructura o tipo de interfaz, se genera un error y no se realizan pasos adicionales.
    • La búsqueda de miembros se realiza con E el identificador Current y sin argumentos de tipo. Si la búsqueda de miembros no produce ninguna coincidencia, el resultado es un error o el resultado es cualquier cosa excepto una propiedad de instancia pública que permita la lectura, se produce un error y no se realizan más pasos.
    • La búsqueda de miembros se realiza con E el identificador MoveNext y sin argumentos de tipo. Si la búsqueda de miembros no produce ninguna coincidencia, el resultado es un error o el resultado es cualquier cosa excepto un grupo de métodos, se genera un error y no se realizan más pasos.
    • La resolución de sobrecarga se realiza en el grupo de métodos con una lista de argumentos vacía. Si la resolución de sobrecargas no da lugar a ningún método aplicable, da como resultado una ambigüedad o da como resultado un único método mejor, pero ese método es estático o no público, o su tipo de valor devuelto no booles , se genera un error y no se realizan pasos adicionales.
    • El tipo de colección es X, el tipo de enumerador es Ey el tipo de iteración es el tipo de la Current propiedad . La Current propiedad puede incluir el ref modificador, en cuyo caso, la expresión devuelta es un variable_reference (§9.5) que es opcionalmente de solo lectura.
  • De lo contrario, compruebe si hay una interfaz enumerable:
    • Si entre todos los tipos Tᵢ para los que hay una conversión implícita de X a IEnumerable<Tᵢ>, hay un tipo T único, de modo que T no dynamic es y para el resto Tᵢ hay una conversión implícita de IEnumerable<T> a IEnumerable<Tᵢ>, el tipo de colección es la interfaz IEnumerable<T>, el tipo de enumerador es la interfaz IEnumerator<T>y el tipo de iteración es T.
    • De lo contrario, si hay más de un tipo de este tipo T, se produce un error y no se realizan pasos adicionales.
    • De lo contrario, si hay una conversión implícita de X a la System.Collections.IEnumerable interfaz, el tipo de colección es esta interfaz, el tipo de enumerador es la interfaz System.Collections.IEnumeratory el tipo de iteración es object.
    • De lo contrario, se produce un error y no se realizan pasos adicionales.

Los pasos anteriores, si se realiza correctamente, generan de forma inequívoca un tipo de colección , el tipo Cde enumerador y el tipo E Tde iteración , ref To ref readonly T. Instrucción foreach del formulario

foreach (V v in x) «embedded_statement»

es equivalente a:

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

La variable e no es visible para la expresión o accesible para la expresión x o la instrucción insertada o cualquier otro código fuente del programa. La variable v es de solo lectura en la instrucción insertada. Si no hay una conversión explícita (§10.3) de T (el tipo de iteración) a V (el local_variable_type de la foreach instrucción ), se genera un error y no se realizan pasos adicionales.

Cuando la variable de iteración es una variable de referencia (§9.7), una foreach instrucción del formulario

foreach (ref V v in x) «embedded_statement»

es equivalente a:

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            ref V v = ref e.Current;
            «embedded_statement»
        }
    }
    finally
    {
        ... // Dispose e
    }
}

La variable e no es visible o accesible para la expresión x o la instrucción insertada o cualquier otro código fuente del programa. La variable v de referencia es de lectura y escritura en la instrucción insertada, pero v no se reasignará a la referencia (§12.21.3). Si no hay una conversión de identidad (§10.2.2) de T (el tipo de iteración) a V (el local_variable_type de la foreach instrucción), se genera un error y no se realizan pasos adicionales.

Una foreach instrucción del formulario foreach (ref readonly V v in x) «embedded_statement» tiene un formato equivalente similar, pero la variable v de referencia está ref readonly en la instrucción insertada y, por tanto, no se puede reasignar o reasignar.

Nota: Si x tiene el valor null, se produce una System.NullReferenceException excepción en tiempo de ejecución. nota final

Se permite que una implementación implemente una foreach_statement determinada de forma diferente; por ejemplo, por motivos de rendimiento, siempre que el comportamiento sea coherente con la expansión anterior.

La colocación de v dentro del while bucle es importante para la forma en que se captura (§12.19.6.2) por cualquier función anónima que se produzca en el embedded_statement.

Ejemplo:

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 en el formato expandido se declarase fuera del while bucle, se compartiría entre todas las iteraciones y su valor después del for bucle sería el valor final, 13, que es lo que la invocación de f imprimiría. En su lugar, dado que cada iteración tiene su propia variable v, la capturada por f en la primera iteración seguirá manteniendo el valor 7, que es lo que se imprimirá. (Tenga en cuenta que las versiones anteriores de C# se declararon v fuera del while bucle).

ejemplo final

El cuerpo del finally bloque se construye según los pasos siguientes:

  • Si hay una conversión implícita de E a la System.IDisposable interfaz,

    • Si E es un tipo de valor que no acepta valores NULL, la finally cláusula se expande al equivalente semántico de:

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • De lo contrario, la finally cláusula se expande al equivalente semántico de:

      finally
      {
          System.IDisposable d = e as System.IDisposable;
          if (d != null)
          {
              d.Dispose();
          }
      }
      

      excepto que si E es un tipo de valor o un parámetro de tipo creado en una instancia de un tipo de valor, la conversión de e en System.IDisposable no hará que se produzca la conversión boxing.

  • De lo contrario, si E es un tipo sellado, la finally cláusula se expande a un bloque vacío:

    finally {}
    
  • De lo contrario, la finally cláusula se expande a:

    finally
    {
        System.IDisposable d = e as System.IDisposable;
        if (d != null)
        {
            d.Dispose();
        }
    }
    

La variable d local no es visible ni accesible para ningún código de usuario. En concreto, no entra en conflicto con ninguna otra variable cuyo ámbito incluya el finally bloque.

El orden en el que foreach atraviesa los elementos de una matriz es el siguiente: Para los elementos de matrices unidimensionales se recorren en orden de índice creciente, empezando por el índice 0 y finalizando con el índice Length – 1. En el caso de las matrices multidimensionales, los elementos se recorren de forma que los índices de la dimensión situada más a la derecha aumentan primero, luego la siguiente dimensión izquierda, etc. a la izquierda.

Ejemplo: en el ejemplo siguiente se imprime cada valor de una matriz bidimensional, en orden de elemento:

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 salida generada es la siguiente:

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

ejemplo final

Ejemplo: En el ejemplo siguiente

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

el tipo de n se deduce como int, el tipo de iteración de numbers.

ejemplo final

13.10 Instrucciones jump

13.10.1 General

Las instrucciones jump transfieren incondicionalmente el control.

jump_statement
    : break_statement
    | continue_statement
    | goto_statement
    | return_statement
    | throw_statement
    ;

La ubicación a la que una instrucción jump transfiere el control se denomina destino de la instrucción jump.

Cuando se produce una instrucción jump dentro de un bloque y el destino de esa instrucción jump está fuera de ese bloque, se dice que la instrucción jump sale del bloque. Aunque una instrucción jump puede transferir el control fuera de un bloque, nunca puede transferir el control a un bloque.

La ejecución de instrucciones de salto es complicada por la presencia de instrucciones intermedias try . En ausencia de estas try instrucciones, una instrucción jump transfiere incondicionalmente el control de la instrucción jump a su destino. En presencia de estas instrucciones intermedias try , la ejecución es más compleja. Si la instrucción jump sale de uno o varios try bloques con bloques asociados finally , el control se transfiere inicialmente al finally bloque de la instrucción más try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los finally bloques de todas las instrucciones try intermedias.

Ejemplo: en el código siguiente

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");
    }
}

Los finally bloques asociados a dos try instrucciones se ejecutan antes de transferir el control al destino de la instrucción jump. La salida generada es la siguiente:

Before break
Innermost finally block
Outermost finally block
After break

ejemplo final

13.10.2 Instrucción break

La break instrucción sale de la instrucción más cercana que incluye switch, while, do, foro foreach .

break_statement
    : 'break' ';'
    ;

El destino de una break instrucción es el punto final de la instrucción más cercana que incluye switch, while, do, foro foreach . Si una break instrucción no está incluida en una switchinstrucción , while, do, o forforeach , se produce un error en tiempo de compilación.

Cuando varias switchinstrucciones , while, do, foro foreach se anidan entre sí, una break instrucción solo se aplica a la instrucción más interna. Para transferir el control entre varios niveles de anidamiento, se usará una goto instrucción (§13.10.4).

Una break instrucción no puede salir de un finally bloque (§13.11). Cuando se produce una break instrucción dentro de un finally bloque, el destino de la break instrucción estará dentro del mismo finally bloque; de lo contrario, se produce un error en tiempo de compilación.

Se ejecuta una break instrucción como se indica a continuación:

  • Si la instrucción sale de uno o varios try bloques con bloques asociadosfinally, el break control se transfiere inicialmente al finally bloque de la instrucción más try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los finally bloques de todas las instrucciones try intermedias.
  • El control se transfiere al destino de la break instrucción .

Dado que una break instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una break instrucción nunca es accesible.

13.10.3 Instrucción continue

La continue instrucción inicia una nueva iteración de la instrucción envolvente whilemás cercana, , do, foro foreach .

continue_statement
    : 'continue' ';'
    ;

El destino de una continue instrucción es el punto final de la instrucción incrustada de la instrucción insertada más cercana que incluye while, do, o forforeach . Si una continue instrucción no está incluida en una whileinstrucción , do, foro foreach , se produce un error en tiempo de compilación.

Cuando varias whileinstrucciones , do, foro foreach se anidan entre sí, una continue instrucción solo se aplica a la instrucción más interna. Para transferir el control entre varios niveles de anidamiento, se usará una goto instrucción (§13.10.4).

Una continue instrucción no puede salir de un finally bloque (§13.11). Cuando se produce una continue instrucción dentro de un finally bloque, el destino de la continue instrucción estará dentro del mismo finally bloque; de lo contrario, se produce un error en tiempo de compilación.

Se ejecuta una continue instrucción como se indica a continuación:

  • Si la instrucción sale de uno o varios try bloques con bloques asociadosfinally, el continue control se transfiere inicialmente al finally bloque de la instrucción más try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los finally bloques de todas las instrucciones try intermedias.
  • El control se transfiere al destino de la continue instrucción .

Dado que una continue instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una continue instrucción nunca es accesible.

13.10.4 La instrucción goto

La goto instrucción transfiere el control a una instrucción marcada por una etiqueta.

goto_statement
    : 'goto' identifier ';'
    | 'goto' 'case' constant_expression ';'
    | 'goto' 'default' ';'
    ;

El destino de una goto instrucción de identificador es la instrucción etiquetada con la etiqueta especificada. Si una etiqueta con el nombre especificado no existe en el miembro de función actual o si la goto instrucción no está dentro del ámbito de la etiqueta, se produce un error en tiempo de compilación.

Nota: Esta regla permite el uso de una goto instrucción para transferir el control fuera de un ámbito anidado, pero no en un ámbito anidado. En el ejemplo

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}]");
        }
    }
}

Se usa una goto instrucción para transferir el control fuera de un ámbito anidado.

nota final

El destino de una goto case instrucción es la lista de instrucciones de la instrucción envolvente switch inmediatamente (§13.8.3), que contiene una case etiqueta con un patrón constante del valor constante especificado y sin protección. Si la goto case instrucción no está incluida en una switch instrucción , si la instrucción envolvente switch más cercana no contiene este tipo caseo si el constant_expression no es implícitamente convertible (§10.2) al tipo de gobernanza de la instrucción envolvente switch más cercana, se produce un error en tiempo de compilación.

El destino de una goto default instrucción es la lista de instrucciones de la instrucción envolvente switch inmediatamente (§13.8.3), que contiene una default etiqueta. Si la goto default instrucción no está incluida en una switch instrucción o si la instrucción envolvente switch más cercana no contiene una etiqueta, se produce un default error en tiempo de compilación.

Una goto instrucción no puede salir de un finally bloque (§13.11). Cuando se produce una goto instrucción dentro de un finally bloque, el destino de la goto instrucción estará dentro del mismo finally bloque o, de lo contrario, se producirá un error en tiempo de compilación.

Se ejecuta una goto instrucción como se indica a continuación:

  • Si la instrucción sale de uno o varios try bloques con bloques asociadosfinally, el goto control se transfiere inicialmente al finally bloque de la instrucción más try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los finally bloques de todas las instrucciones try intermedias.
  • El control se transfiere al destino de la goto instrucción .

Dado que una goto instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una goto instrucción nunca es accesible.

13.10.5 La instrucción return

La return instrucción devuelve el control al autor de la llamada actual del miembro de función en el que aparece la instrucción return, devolviendo opcionalmente un valor o un variable_reference (§9.5).

return_statement
    : 'return' ';'
    | 'return' expression ';'
    | 'return' 'ref' variable_reference ';'
    ;

Un return_statement sin expresión se denomina return-no-value; una expresión contenedora ref se denomina return-by-ref; y una que contiene solo expresión se denomina return-by-value.

Se trata de un error en tiempo de compilación para usar un valor devuelto sin valor de un método declarado como devuelto por valor o return-by-ref (§15.6.1).

Se trata de un error en tiempo de compilación para usar un valor devuelto por referencia de un método declarado como returns-no-value o returns-by-value.

Se trata de un error en tiempo de compilación para usar un valor devuelto por valor de un método declarado como returns-no-value o returns-by-ref.

Se trata de un error en tiempo de compilación para usar un valor return-by-ref si la expresión no es una variable_reference o es una referencia a una variable cuyo contexto ref-safe-context no es llamador-context (§9.7.2).

Se trata de un error en tiempo de compilación para usar un valor return-by-ref de un método declarado con el method_modifier async.

Se dice que un miembro de función calcula un valor si es un método con un método devuelto por valor (§15.6.11), un descriptor de acceso get devuelto por valor de una propiedad o indexador, o un operador definido por el usuario. Los miembros de función que son return-no-value no calculan un valor y son métodos con el tipo voidde valor devuelto efectivo , descriptores de acceso set de propiedades e indizadores, agregar y quitar descriptores de acceso de eventos, constructores de instancias, constructores estáticos y finalizadores. Los miembros de función que se devuelven por referencia no calculan un valor.

Para un valor devuelto, una conversión implícita (§10.2) existirá del tipo de expresión al tipo de valor devuelto efectivo (§15.6.11) del miembro de función contenedor. Para una devolución por referencia, una conversión de identidad (§10.2.2) existirá entre el tipo de expresión y el tipo de valor devuelto efectivo del miembro de función contenedor.

return Las instrucciones también se pueden usar en el cuerpo de expresiones de función anónimas (§12.19) y participar en determinar qué conversiones existen para esas funciones (§10.7.1).

Es un error en tiempo de compilación para que una return instrucción aparezca en un finally bloque (§13.11).

Se ejecuta una return instrucción como se indica a continuación:

  • Para un valor devuelto por valor, la expresión se evalúa y su valor se convierte en el tipo de valor devuelto efectivo de la función contenedora mediante una conversión implícita. El resultado de la conversión se convierte en el valor de resultado generado por la función . Para un valor return-by-ref, la expresión se evalúa y el resultado se clasificará como una variable. Si el método de inclusión devuelto por ref incluye readonly, la variable resultante es de solo lectura.
  • Si la instrucción está entre uno o varios bloques o catch bloques con bloques asociadosfinally, el return control se transfiere inicialmente al finally bloque de la instrucción más try try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los bloques de todas las finally instrucciones try envolventes.
  • Si la función contenedora no es una función asincrónica, el control se devuelve al autor de la llamada de la función contenedora junto con el valor de resultado, si existe.
  • Si la función contenedora es una función asincrónica, el control se devuelve al autor de la llamada actual y el valor de resultado, si existe, se registra en la tarea de devolución, tal como se describe en (§15.15.3).

Dado que una return instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una return instrucción nunca es accesible.

13.10.6 Instrucción throw

La throw instrucción produce una excepción.

throw_statement
    : 'throw' expression? ';'
    ;

Una throw instrucción con una expresión produce una excepción generada mediante la evaluación de la expresión. La expresión se convertirá implícitamente en System.Exceptiony el resultado de evaluar la expresión se convierte en System.Exception antes de iniciarse. Si el resultado de la conversión es null, se produce una System.NullReferenceException excepción en su lugar.

Una throw instrucción sin expresión solo se puede usar en un catch bloque, en cuyo caso, esa instrucción vuelve a producir la excepción que está siendo controlada actualmente por ese catch bloque.

Dado que una throw instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una throw instrucción nunca es accesible.

Cuando se produce una excepción, el control se transfiere a la primera catch cláusula en una instrucción envolvente try que puede controlar la excepción. El proceso que tiene lugar desde el punto de la excepción que se inicia hasta el punto de transferir el control a un controlador de excepciones adecuado se conoce como propagación de excepciones. La propagación de una excepción consiste en evaluar repetidamente los pasos siguientes hasta que se encuentre una catch cláusula que coincida con la excepción. En esta descripción, el punto de inicio es inicialmente la ubicación en la que se produce la excepción. Este comportamiento se especifica en (§21.4).

  • En el miembro de función actual, se examina cada try instrucción que incluye el punto de lanzamiento. Para cada instrucción S, empezando por la instrucción más try interna y terminando con la instrucción más try externa, se evalúan los pasos siguientes:

    • Si el try bloque de incluye el punto de S inicio y si S tiene una o varias catch cláusulas, las catch cláusulas se examinan en orden de apariencia para buscar un controlador adecuado para la excepción. La primera catch cláusula que especifica un tipo T de excepción (o un parámetro de tipo que en tiempo de ejecución denota un tipo Tde excepción ), de modo que el tipo de tiempo de ejecución de E deriva de T se considera una coincidencia. Si la cláusula contiene un filtro de excepción, el objeto exception se asigna a la variable de excepción y se evalúa el filtro de excepción. Cuando una catch cláusula contiene un filtro de excepciones, esa catch cláusula se considera una coincidencia si el filtro de excepción se evalúa como true. Una cláusula general catch (§13.11) se considera una coincidencia para cualquier tipo de excepción. Si se encuentra una cláusula coincidente catch , la propagación de excepciones se completa transfiriendo el control al bloque de esa catch cláusula.
    • De lo contrario, si el try bloque o un catch bloque de incluye el punto de S inicio y, si S tiene un finally bloque, el control se transfiere al finally bloque. Si el bloque finally genera otra excepción, finaliza el procesamiento de la excepción actual. De lo contrario, cuando el control alcanza el punto final del finally bloque, se continúa el procesamiento de la excepción actual.
  • Si un controlador de excepciones no se encuentra en la invocación de función actual, la invocación de función finaliza y se produce una de las siguientes acciones:

    • Si la función actual no es asincrónica, los pasos anteriores se repiten para el autor de la llamada de la función con un punto de inicio correspondiente a la instrucción desde la que se invocó el miembro de función.

    • Si la función actual es asincrónica y devuelve tareas, la excepción se registra en la tarea de devolución, que se coloca en un estado con errores o cancelados, tal y como se describe en §15.15.3.

    • Si la función actual es asincrónica y void-devuelve, se notifica al contexto de sincronización del subproceso actual como se describe en §15.15.4.

  • Si el procesamiento de excepciones finaliza todas las invocaciones de miembro de función en el subproceso actual, lo que indica que el subproceso no tiene ningún controlador para la excepción, el subproceso finaliza. El impacto de dicha terminación está definido por la implementación.

13.11 Instrucción try

La try instrucción proporciona un mecanismo para detectar excepciones que se producen durante la ejecución de un bloque. Además, la try instrucción proporciona la capacidad de especificar un bloque de código que siempre se ejecuta cuando el control deja la try instrucción .

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
    ;

Un try_statement consta de la palabra clave try seguida de un bloque, cero o más catch_clauses y, a continuación, un finally_clause opcional. Habrá al menos un catch_clause o un finally_clause.

En un exception_specifier el tipo, o su clase base efectiva si es un type_parameter, será System.Exception o un tipo que derive de él.

Cuando una catch cláusula especifica un class_type y un identificador, se declara una variable de excepción del nombre y el tipo especificados. La variable de excepción se introduce en el espacio de declaración del specific_catch_clause (§7.3). Durante la ejecución del exception_filter y catch el bloque , la variable de excepción representa la excepción que se está controlando actualmente. Para fines de comprobación de asignaciones definitivas, la variable de excepción se considera definitivamente asignada en todo su ámbito.

A menos que una catch cláusula incluya un nombre de variable de excepción, es imposible tener acceso al objeto de excepción en el filtro y catch el bloque.

Una catch cláusula que especifica ni un tipo de excepción ni un nombre de variable de excepción se denomina cláusula general catch . Una try instrucción solo puede tener una cláusula general catch y, si existe, será la última catch cláusula.

Nota: Algunos lenguajes de programación pueden admitir excepciones que no se pueden representar como un objeto derivado de System.Exception, aunque el código de C# nunca podría generar dichas excepciones. Es posible que se use una cláusula general catch para detectar estas excepciones. Por lo tanto, una cláusula general catch es semánticamente diferente de una que especifica el tipo System.Exception, en que el anterior también podría detectar excepciones de otros lenguajes. nota final

Para localizar un controlador para una excepción, catch las cláusulas se examinan en orden léxico. Si una catch cláusula especifica un tipo pero no un filtro de excepciones, se trata de un error en tiempo de compilación para una cláusula posterior catch de la misma try instrucción para especificar un tipo que sea el mismo que o se derive de ese tipo.

Nota: Sin esta restricción, sería posible escribir cláusulas inaccesibles catch . nota final

Dentro de un catch bloque, se puede usar una throw instrucción (§13.10.6) sin ninguna expresión para volver a iniciar la excepción detectada por el catch bloque. Las asignaciones a una variable de excepción no modifican la excepción que se vuelve a producir.

Ejemplo: en el código siguiente

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);
        }
    }
}

El método F detecta una excepción, escribe información de diagnóstico en la consola, modifica la variable de excepción y vuelve a iniciar la excepción. La excepción que se vuelve a producir es la excepción original, por lo que la salida generada es:

Exception in F: G
Exception in Main: G

Si el primer catch bloque se había producido e en lugar de volver a iniciar la excepción actual, la salida generada sería la siguiente:

Exception in F: G
Exception in Main: F

ejemplo final

Es un error en tiempo de compilación para que una breakinstrucción , continueo goto transfiera el control fuera de un finally bloque. Cuando se produce una breakinstrucción , continueo goto en un finally bloque, el destino de la instrucción estará dentro del mismo finally bloque o, de lo contrario, se producirá un error en tiempo de compilación.

Se trata de un error en tiempo de compilación para que se produzca una return instrucción en un finally bloque.

Cuando la ejecución alcanza una try instrucción, el control se transfiere al try bloque . Si el control alcanza el punto final del try bloque sin que se propague una excepción, el control se transfiere al finally bloque si existe uno. Si no existe ningún finally bloque, el control se transfiere al punto final de la try instrucción .

Si se ha propagado una excepción, las catch cláusulas, si las hay, se examinan en orden léxico buscando la primera coincidencia para la excepción. La búsqueda de una cláusula coincidente catch continúa con todos los bloques envolventes como se describe en §13.10.6. Una catch cláusula es una coincidencia si el tipo de excepción coincide con cualquier exception_specifier y cualquier exception_filter es true. Una catch cláusula sin un exception_specifier coincide con ningún tipo de excepción. El tipo de excepción coincide con el exception_specifier cuando el exception_specifier especifica el tipo de excepción o un tipo base del tipo de excepción. Si la cláusula contiene un filtro de excepción, el objeto exception se asigna a la variable de excepción y se evalúa el filtro de excepción.

Si se ha propagado una excepción y se encuentra una cláusula coincidente catch , el control se transfiere al primer bloque coincidente catch . Si el control alcanza el punto final del catch bloque sin que se propague una excepción, el control se transfiere al finally bloque si existe uno. Si no existe ningún finally bloque, el control se transfiere al punto final de la try instrucción . Si se ha propagado una excepción desde el catch bloque, el control se transfiere al finally bloque si existe uno. La excepción se propaga a la siguiente instrucción envolvente try .

Si se ha propagado una excepción y no se encuentra ninguna cláusula coincidente catch , el control se transfiere al finally bloque, si existe. La excepción se propaga a la siguiente instrucción envolvente try .

Las instrucciones de un bloque finally siempre se ejecutan cuando el control sale de una instrucción try. Esto es cierto si la transferencia de control se produce como resultado de la ejecución normal, como resultado de ejecutar una breakinstrucción , continue, gotoo return , o como resultado de propagar una excepción fuera de la try instrucción . Si el control alcanza el punto final del finally bloque sin que se propague una excepción, el control se transfiere al punto final de la try instrucción .

Si se produce una excepción durante la ejecución de un finally bloque y no se detecta dentro del mismo finally bloque, la excepción se propaga a la siguiente instrucción envolvente try . Si otra excepción estaba en proceso de propagación, esa excepción se pierde. El proceso de propagación de una excepción se describe más adelante en la descripción de la throw instrucción (§13.10.6).

Ejemplo: en el código siguiente

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");
        }
    }
}

El método Method produce una excepción. La primera acción consiste en examinar las cláusulas envolventes catch , ejecutando los filtros de excepción. A continuación, la finally cláusula de se Method ejecuta antes de que el control se transfiera a la cláusula coincidente catch envolvente. La salida resultante es:

Filter
Finally
Catch

ejemplo final

El try bloque de una try instrucción es accesible si la try instrucción es accesible.

Se catch puede acceder a un bloque de una try instrucción si se puede acceder a la try instrucción .

El finally bloque de una try instrucción es accesible si la try instrucción es accesible.

El punto final de una try instrucción es accesible si se cumplen las dos condiciones siguientes:

  • El punto final del try bloque es accesible o se puede acceder al punto final de al menos un catch bloque.
  • Si hay un finally bloque presente, se puede acceder al punto final del finally bloque.

13.12 Instrucciones activadas y desactivadas

Las checked instrucciones y unchecked se usan para controlar el contexto de comprobación de desbordamiento para operaciones y conversiones aritméticas de tipo entero.

checked_statement
    : 'checked' block
    ;

unchecked_statement
    : 'unchecked' block
    ;

La checked instrucción hace que todas las expresiones del bloque se evalúen en un contexto comprobado y la unchecked instrucción hace que todas las expresiones del bloque se evalúen en un contexto sin marcar.

Las checked instrucciones y unchecked son exactamente equivalentes a los checked operadores y unchecked (§12.8.20), excepto que funcionan en bloques en lugar de expresiones.

13.13 Instrucción lock

La lock instrucción obtiene el bloqueo de exclusión mutua para un objeto determinado, ejecuta una instrucción y, a continuación, libera el bloqueo.

lock_statement
    : 'lock' '(' expression ')' embedded_statement
    ;

La expresión de una lock instrucción indicará un valor de un tipo conocido como una referencia. No se realiza ninguna conversión de conversión boxing implícita (§10.2.9) para la expresión de una lock instrucción y, por tanto, es un error en tiempo de compilación para que la expresión denota un valor de un value_type.

Instrucción lock del formulario

lock (x)

donde x es una expresión de un reference_type, es exactamente equivalente a:

bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally
{
    if (__lockWasTaken)
    {
        System.Threading.Monitor.Exit(x);
    }
}

salvo que x solo se evalúa una vez.

Aunque se mantiene un bloqueo de exclusión mutua, el código que se ejecuta en el mismo subproceso de ejecución también puede obtener y liberar el bloqueo. Sin embargo, se impide que el código que se ejecute en otros subprocesos obtenga el bloqueo hasta que se libere el bloqueo.

13.14 Instrucción using

La using instrucción obtiene uno o varios recursos, ejecuta una instrucción y, a continuación, elimina el recurso.

using_statement
    : 'using' '(' resource_acquisition ')' embedded_statement
    ;

resource_acquisition
    : local_variable_declaration
    | expression
    ;

Un recurso es una clase o estructura que implementa la System.IDisposable interfaz, que incluye un único método sin parámetros denominado Dispose. El código que usa un recurso puede llamar Dispose a para indicar que el recurso ya no es necesario.

Si la forma de resource_acquisition es local_variable_declaration, el tipo del local_variable_declaration será o dynamic un tipo que se pueda convertir implícitamente en System.IDisposable. Si la forma de resource_acquisition es expresión , esta expresión se podrá convertir implícitamente en System.IDisposable.

Las variables locales declaradas en un resource_acquisition son de solo lectura e incluirán un inicializador. Se produce un error en tiempo de compilación si la instrucción insertada intenta modificar estas variables locales (a través de la asignación o los ++ operadores y -- ), tomar la dirección de ellas o pasarlas como parámetros de referencia o salida.

Una using instrucción se traduce en tres partes: adquisición, uso y eliminación. El uso del recurso se incluye implícitamente en una try instrucción que incluye una finally cláusula . Esta finally cláusula elimina el recurso. Si se adquiere un null recurso, no se realiza ninguna llamada a Dispose y no se produce ninguna excepción. Si el recurso es de tipo dynamic , se convierte dinámicamente a través de una conversión dinámica implícita (§10.2.10) a IDisposable durante la adquisición para asegurarse de que la conversión se realiza correctamente antes del uso y eliminación.

Instrucción using del formulario

using (ResourceType resource = «expression» ) «statement»

corresponde a una de las tres expansiones posibles. Cuando ResourceType es un tipo de valor que no acepta valores NULL o un parámetro de tipo con la restricción de tipo de valor (§15.2.5), la expansión es semánticamente equivalente a

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        ((IDisposable)resource).Dispose();
    }
}

salvo que la conversión de resource en System.IDisposable no hará que se produzca la conversión boxing.

De lo contrario, cuando ResourceType es dynamic, la expansión es

{
    ResourceType resource = «expression»;
    IDisposable d = resource;
    try
    {
        «statement»;
    }
    finally
    {
        if (d != null)
        {
            d.Dispose();
        }
    }
}

De lo contrario, la expansión es

{
    ResourceType resource = «expression»;
    try
    {
        «statement»;
    }
    finally
    {
        IDisposable d = (IDisposable)resource;
        if (d != null)
        {
            d.Dispose();
        }
    }
}

En cualquier expansión, la resource variable es de solo lectura en la instrucción insertada, y la d variable es inaccesible en la instrucción insertada y es invisible para ella.

Se permite que una implementación implemente una using_statement determinada de forma diferente, por ejemplo, por motivos de rendimiento, siempre que el comportamiento sea coherente con la expansión anterior.

Una using instrucción del formulario:

using («expression») «statement»

tiene las mismas tres expansiones posibles. En este caso ResourceType , es implícitamente el tipo en tiempo de compilación de la expresión, si tiene uno. De lo contrario, la propia interfaz IDisposable se usa como .ResourceType La resource variable no es accesible en la instrucción insertada y es invisible.

Cuando un resource_acquisition adopta la forma de un local_variable_declaration, es posible adquirir varios recursos de un tipo determinado. Instrucción using del formulario

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»

es exactamente equivalente a una secuencia de instrucciones anidadas using :

using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»

Ejemplo: En el ejemplo siguiente se crea un archivo denominado log.txt y se escriben dos líneas de texto en el archivo. A continuación, se abre ese mismo archivo para leer y copiar las líneas de texto contenidas en la consola.

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);
            }
        }
    }
}

Dado que las TextWriter clases y TextReader implementan la IDisposable interfaz , el ejemplo puede usar using instrucciones para asegurarse de que el archivo subyacente está cerrado correctamente después de las operaciones de escritura o lectura.

ejemplo final

13.15 Instrucción yield

La yield instrucción se usa en un bloque de iterador (§13.3) para producir un valor para el objeto enumerador (§15.14.5) o un objeto enumerable (§15.14.6) de un iterador o para indicar el final de la iteración.

yield_statement
    : 'yield' 'return' expression ';'
    | 'yield' 'break' ';'
    ;

yield es una palabra clave contextual (§6.4.4) y tiene un significado especial solo cuando se usa inmediatamente antes de una return palabra clave o break .

Hay varias restricciones sobre dónde puede aparecer una yield instrucción, como se describe en lo siguiente.

  • Es un error en tiempo de compilación para que una yield instrucción (de cualquiera de las formas) aparezca fuera de un method_body, operator_body o accessor_body.
  • Es un error en tiempo de compilación para que una yield instrucción (de cualquiera de las formas) aparezca dentro de una función anónima.
  • Es un error en tiempo de compilación para que una yield instrucción (de cualquiera de las formas) aparezca en la finally cláusula de una try instrucción .
  • Es un error en tiempo de compilación para que una yield return instrucción aparezca en cualquier parte de una try instrucción que contenga cualquier catch_clauses.

Ejemplo: en el ejemplo siguiente se muestran algunos usos válidos e no válidos de yield instrucciones.

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
}

ejemplo final

Una conversión implícita (§10.2) existirá del tipo de expresión de la yield return instrucción al tipo de rendimiento (§15.14.4) del iterador.

Se ejecuta una yield return instrucción como se indica a continuación:

  • La expresión dada en la instrucción se evalúa, se convierte implícitamente en el tipo de rendimiento y se asigna a la Current propiedad del objeto enumerador.
  • Se suspende la ejecución del bloque de iterador. Si la yield return instrucción está dentro de uno o varios try bloques, los bloques asociados finally no se ejecutan en este momento.
  • El MoveNext método del objeto enumerador vuelve true a su llamador, lo que indica que el objeto enumerador ha avanzado correctamente al siguiente elemento.

La siguiente llamada al método del MoveNext objeto enumerador reanuda la ejecución del bloque iterador desde donde se suspendió por última vez.

Se ejecuta una yield break instrucción como se indica a continuación:

  • Si la instrucción está entre uno o varios try bloques con bloques asociadosfinally, el yield break control se transfiere inicialmente al finally bloque de la instrucción más try interna. Cuando y si el control llega al punto final de un finally bloque, el control se transfiere al bloque de la finally siguiente instrucción envolvente try . Este proceso se repite hasta que se han ejecutado los bloques de todas las finally instrucciones try envolventes.
  • El control se devuelve al autor de la llamada del bloque de iterador. Este es el método o Dispose el MoveNext método del objeto enumerador.

Dado que una yield break instrucción transfiere incondicionalmente el control en otro lugar, el punto final de una yield break instrucción nunca es accesible.