Compartir vía


13 Declaraciones

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 elemento no terminal embedded_statement se usa para instrucciones que aparecen dentro de otras instrucciones. El uso de embedded_statement en lugar de una 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 la declaración de i en un bloque, el ejemplo es válido.

ejemplo final

13.2 Puntos finales y accesibilidad

Cada instrucción tiene un punto final. En términos intuitivos, el punto final de una declaración es la ubicación que sigue inmediatamente a la declaració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 una instrucción distinta a throw_statement, block o empty_statement es inalcanzable. En concreto, no es un error que una declaración sea inalcanzable.

Nota: Para determinar si una instrucción determinada o un punto de conexión es accesible, un 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 produciendo el valor false, se considera que la invocación Console.WriteLine es 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 accesibilidad 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 es un error de tiempo de compilación que el punto final de una instrucción sea alcanzable.

  • Dado que la instrucción switch no permite que una sección switch "se salte" a la siguiente, es un error en tiempo de compilación que el punto final de la lista de instrucciones de una sección switch sea alcanzable. 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), encerrada 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.15). Algunas restricciones adicionales se aplican a los bloques de iterador:

  • Es un error en tiempo de compilación que una instrucción return aparezca en un bloque de iterador, pero se permiten las instrucciones yield return.
  • Es un error en tiempo de compilación que un bloque de iterador contenga un contexto inseguro (§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 declaraciones

Una lista de instrucciones consta de una o varias instrucciones escritas en secuencia. Las listas de instrucciones se encuentran en bloques (§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.
  • El punto final de la declaración anterior es accesible.
  • La instrucción es una instrucción etiquetada y la etiqueta es referenciada por una instrucción goto accesible .

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, el punto final de una instrucción vacía es accesible si la propia instrucción vacía lo es.

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. Es un error en tiempo de compilación que dos etiquetas con el mismo nombre tengan alcances 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 esta opción.
  • 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 una 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, con tipo explícito 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 que no ocurre 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 una resource_acquisition, entonces 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 corresponde al 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 declaración o dentro de cualquier expresión de inicialización dentro de su declaración. 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 ref-safe-context (§9.7.2) de una variable local ref es el ref-safe-context de su variable_reference de inicialización. El ref-safe-context de las variables locales no referenciadas 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ónreferencia 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_referencerefreferencia que no acepta valores NULL; de lo contrario, su tipo es . (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

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 variable_reference de inicialización deberá tener el tipo type y cumplirá los mismos requisitos que una asignación de referencia (§12.21.3).

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

Es un error de compilación declarar una variable local ref o una variable de un tipo ref struct dentro de un método declarado con el method_modifierasync, o dentro de un iterador (§15.15).

13.6.3 Declaraciones de constantes locales

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_expressioncomo 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 a 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 las local_function_declaration sobrecargadas.

Un local_function_declaration puede incluir un async modificador (§15.14) 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.14.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. Es un error en tiempo de compilación 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 dentro del ámbito de un 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. Un 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 a this. Esto es cierto si el acceso es explícito (como en this.x) o implícito (como en donde xx 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 de compilación que el cuerpo de la función local contenga una sentencia goto, una sentencia break o una sentencia continue 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 declarar léxicamente la función antes de la declaración de una variable utilizada en la función local (§7.7).

Es un error de compilación 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 entorno de declaración de variables locales.

Los cuerpos de función locales siempre son accesibles. El punto final de una declaración de función local es accesible si el punto inicial de la declaración de función local es accesible.

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 al alcance de las instrucciones de la función que la contiene. 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 deberá hacer referencia a this, base, ni a miembros de instancia de una referencia implícita this, ni a variables locales, parámetros o funciones locales no estáticas desde el ámbito envolvente. Sin embargo, todas estas se permiten en una nameof() expresión.

13.7 Instrucciones de expresión

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

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 de selección

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 La 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 parte else está asociada con el elemento léxico más cercano anterior if permitido por la sintaxis.

Ejemplo: Por lo tanto, una instrucción if 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 la expresión booleana (§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 alcanzable si la if instrucción es alcanzable y la expresión booleana no tiene el valor constante false.

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

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 parte es accesible si la if instrucción es accesible y la expresión booleana no tiene constante el valor true.

13.8.3 Instrucción switch

La switch instrucción selecciona para ejecutar una lista de instrucciones que tiene una etiqueta de switch 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 consiste en la palabra clave switch, seguido de una expresión entre paréntesis (denominada switch_expression), seguida de un switch_block. El switch_block consta de cero o más switch_sections, encerradas entre llaves. Cada switch_section consta de una o varias switch_labelseguidas de un statement_list (§13.3.2). Cada switch_label que contiene tiene un patrón asociado (case) 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, short, ushort, int, uint, long, ulong, char, bool, string o un enum_type, o si es el tipo de valor anulable correspondiente a uno de estos tipos, entonces ese es el tipo de la instrucción switch.
  • 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, short, ushort, int, uint, long, ulong, char, o string, o un tipo de valor anulable correspondiente a uno de esos tipos, entonces el tipo convertido es el tipo de gobierno de la instrucción switch.
  • De lo contrario, el tipo rector de la instrucción switch es el tipo de la expresión switch. Se trata de un error si no existe este tipo.

Puede haber a lo sumo una etiqueta default en una instrucción switch.

Se trata de un error si el patrón de cualquier etiqueta de interruptor 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 switch está incluido por (§11.3) el conjunto de patrones de etiquetas switch anteriores de la instrucción switch que no tienen una restricción de caso o cuya restricción de caso es una expresión constante con el valor verdadero.

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 en el conjunto de etiquetas de case en la misma instrucción switch que coincide con el valor de la expresión switch, y para el que la expresión de restricción está ausente o se evalúa como verdadero, provoca que el control se transfiera a la lista de instrucciones que sigue a la etiqueta case coincidente.
    • 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 destino final de la instrucción switch.

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 está obligado) a hacer coincidir patrones fuera de orden y a reutilizar los resultados de patrones ya coincidentes para calcular el resultado de la coincidencia de otros patrones. Sin embargo, se requiere un compilador para determinar el primer patrón que coincide léxicamente con la expresión y para el que la cláusula de restricción 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 la regla de "no se permite el paso".

Ejemplo: El ejemplo

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

es válido porque ninguna sección del switch tiene un punto final alcanzable. A diferencia de C y C++, la ejecución de una sección switch no puede continuar hacia la siguiente sección switch, 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 deberá usar una instrucción explícita: una declaración goto case o goto default.

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 "no se permite el paso" impide una clase común de errores que se producen en C y C++ cuando las instrucciones break se omiten accidentalmente. Por ejemplo, las secciones de la instrucción switch 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 instrucción break, goto case o goto default, pero se permite cualquier construcción que haga que el punto final de la lista de instrucciones sea 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 declaración o return siempre transfiere el control a otro lugar y nunca alcanza su punto final. 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 instrucción switch distingue entre mayúsculas y minúsculas y ejecutará una sección switch solo si la cadena de expresión switch coincide exactamente con una constante de etiqueta case. nota final Cuando el tipo de gobernanza de una switch instrucción es string o un tipo de valor que acepta valores nulos, 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 puede ser una de las dos opciones siguientes
    • la etiqueta es un case cuyo patrón coincidiría (§11.2.1) ese valor, y la restricción de la etiqueta está ausente o no es una expresión constante con el valor "false"; o bien
    • es una etiqueta default y ninguna sección switch contiene una etiqueta de caso cuyo patrón coincida con ese valor y cuya condición de restricción esté ausente o sea una expresión constante con el valor "true".
  • La expresión switch no es un valor constante y/o
    • la etiqueta es case sin restricción o cuenta con una restricción cuyo valor no es la constante "false"; o
    • es una etiqueta default y
      • el conjunto de patrones que aparecen entre los casos de la instrucción switch que no tienen restricciones o tienen restricciones cuyo valor constante es "true", no es exhaustivo (§11.4) para el tipo que gobierna el switch; o
      • el tipo de control del 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 restricciones o tienen restricciones cuyo valor es la constante "true" no contiene un patrón que coincida con el valor null.
  • Se hace referencia a la etiqueta switch mediante una instrucción goto case o goto default accesible.

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 instrucción switch contiene una instrucción break accesible que termina la instrucción switch.
  • Ninguna etiqueta default 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 restricciones o tienen restricciones cuyo valor es la constante "true", no es exhaustivo (§11.4) para el tipo que gobierna el switch.
    • La expresión switch es un valor no constante de un tipo anulable, y ningún patrón que aparece entre los casos de la instrucción switch sin restricciones, o con restricciones 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 La 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 la expresión booleana (§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 incrustada de una instrucción while, se puede usar una instrucción break (§13.10.2) para transferir el control al punto final de la instrucción while (y así terminar la iteración de la instrucción incrustada), y una instrucción continue (§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 instrucción while).

La instrucción insertada de una while instrucción es alcanzable si la instrucción while es alcanzable y la expresión booleana no tiene el valor constante false.

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

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

13.9.3 La 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 destino final de la instrucción do.

Dentro de la instrucción incrustada de una instrucción do, se puede usar una instrucción break (§13.10.2) para transferir el control al punto final de la instrucción do (y así terminar la iteración de la instrucción incrustada), y una instrucción continue (§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 instrucción do).

La instrucción insertada en una instrucción do es alcanzable si la instrucción do es alcanzable.

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

  • La instrucción do contiene una instrucción break accesible que termina la instrucción do.
  • 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 da como resultado false, el control se transfiere al punto final de la for sentencia.

Dentro de la instrucción incrustada 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 (terminando así la iteración 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, comenzando con el for_condition).

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

  • La for instrucción es accesible y no hay ninguna for_condition presente.
  • La for instrucción es accesible y hay una for_condition presente y no tiene el valor constante false.

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

  • La instrucción for contiene una instrucción break accesible que termina la instrucción for.
  • La for instrucción es accesible y hay una for_condition presente y no tiene el valor constante true.

13.9.5 Instrucción foreach

13.9.5.1 General

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
    : 'await'? '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.

Es un error en tiempo de compilación que await y ref_kind estén presentes en un foreach statement.

Si el foreach_statement contiene tanto ref como readonly, o ninguno de los dos, 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.

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. El procesamiento de una foreach instrucción se detalla en §13.9.5.2 y el proceso de una await foreach instrucción se detalla en §13.9.5.3.

Nota: Si la expresión tiene el valor null, se lanza una excepción System.NullReferenceException 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.

13.9.5.2 foreach sincrónico

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 expresión es un tipo de matriz, hay una conversión de referencia implícita de X a la interfaz IEnumerable (ya que System.Array implementa esta interfaz). El tipo de colección es la interfaz IEnumerable, el tipo de enumerador es la interfaz IEnumerator y el tipo de iteración es el tipo de elemento del tipo de matriz X.
  • 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 consulta de miembros genera algo distinto de un grupo de métodos o no produce 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 cualquier cosa excepto un método de instancia inequívoco y público 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 en E con 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 en E con 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 realizan correctamente, generan de forma inequívoca un tipo de colección C, un tipo de enumerador E y un tipo de iteración T, ref T o ref readonly T. Una instrucción foreach de la forma

foreach (V v in x) «embedded_statement»

entonces 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 ni accesible para la expresión x o la instrucción insertada ni para 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 instrucción foreach de la forma

foreach (ref V v in x) «embedded_statement»

entonces 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 debe reasignarse como 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 instrucción foreach 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 asignar ref o reasignar.

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# declararon v fuera del bucle while).

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 instanciado como un tipo de valor, la conversión de e a System.IDisposable no hará que se produzca la encapsulación.

  • 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.9.5.3 await foreach

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. El procesamiento de una foreach instrucción se detalla en §13.9.5.2 y el proceso de una await foreach instrucción se detalla en §13.9.5.3.

Esta determinación continúa de la siguiente manera:

  • Determine si el tipo X tiene un método adecuado GetAsyncEnumerator :
    • Realice la búsqueda de miembros en el tipo X con identificador GetAsyncEnumerator 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 consulta de miembros genera algo distinto de un grupo de métodos o no produce 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 cualquier cosa excepto un método de instancia inequívoco y público o ningún método aplicable.
    • Si el tipo E de valor devuelto del GetAsyncEnumerator 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 en E con 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 en E con el identificador MoveNextAsync 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 sobrecarga 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 es esperable (§12.9.8.2) donde el await_expression se clasifica como un bool (§12.9.8.3), se produce un error y no se realizan más pasos.
    • 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 .
  • De lo contrario, compruebe si hay una interfaz enumerable asincrónica:
    • Si entre todos los tipos Tᵢ para los que hay una conversión implícita de X a IAsyncEnumerable<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 IAsyncEnumerable<T> a IAsyncEnumerable<Tᵢ>, el tipo de colección es la interfaz IAsyncEnumerable<T>, el tipo de enumerador es la interfaz IAsyncEnumerator<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, se produce un error y no se realizan pasos adicionales.

Los pasos anteriores, si se realizan correctamente, generan de forma inequívoca un tipo de colección C, un tipo de enumerador E y un tipo de iteración T. Una instrucción await foreach de la forma

await foreach (V v in x) «embedded_statement»

entonces es equivalente a:

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

La variable e no es visible ni accesible para la expresión x o la instrucción insertada ni para 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 await foreach instrucción ), se genera un error y no se realizan pasos adicionales.

Un enumerador asincrónico puede exponer opcionalmente un método DisposeAsync que se puede invocar sin argumentos y que devuelve algo que se puede await y cuyo GetResult() devuelve void.

Una instrucción foreach de la forma

await foreach (T item in enumerable) «embedded_statement»

se expande a:

var enumerator = enumerable.GetAsyncEnumerator();
try
{
    while (await enumerator.MoveNextAsync())
    {
       T item = enumerator.Current;
       «embedded_statement»
    }
}
finally
{
    await enumerator.DisposeAsync(); // omitted, along with the try/finally,
                            // if the enumerator doesn't expose DisposeAsync
}

13.10 Instrucciones de salto

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 instrucciones try, una instrucción de salto transfiere incondicionalmente el control de la instrucción de salto a su destino. En presencia de estas instrucciones intermedias try , la ejecución es más compleja. Si la instrucción de salto sale de uno o varios bloques try con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. 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 bloques finally asociados a dos instrucciones try se ejecutan antes de transferir el control al destino de la instrucción de salto. 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 instrucción break sale de la instrucción más cercana que incluye switch, while, do, for o foreach.

break_statement
    : 'break' ';'
    ;

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

Cuando varias instrucciones switch, while, do, for o foreach se anidan entre sí, la instrucción break 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 instrucción break no puede salir de un bloque finally (§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 break sale de uno o varios bloques try con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. 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 instrucción break.

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 instrucción continue inicia una nueva iteración de la instrucción while, do, for o foreach más cercana que la incluye.

continue_statement
    : 'continue' ';'
    ;

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

Cuando varias while, do, for o foreach instrucciones 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 instrucción continue no puede salir de un bloque finally (§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 continue sale de uno o varios bloques try con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. 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 instrucción continue.

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 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 gotoinstrucció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 instrucción goto para transferir el control fuera de un ámbito anidado.

nota final

El destino de una instrucción goto case es la lista de instrucciones de la instrucción switch envolvente inmediatamente (§13.8.3) que contiene una etiqueta case con un patrón constante del valor constante especificado y sin restricción. Si la instrucción goto case no está contenida en una instrucción switch, si la instrucción envolvente switch más cercana no contiene ese tipo de case, o 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 instrucción goto default es la lista de instrucciones en la instrucción envolvente inmediata switch (§13.8.3), que contiene una etiqueta default. 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 default, se produce un error en tiempo de compilación.

Una instrucción goto no puede salir de un bloque finally (§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 goto sale de uno o varios bloques try con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. 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 instrucción goto.

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 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; uno que contiene refexpresión se denomina return-by-ref; y uno que contiene solo expresión se denomina return-by-value.

Se trata de un error en tiempo de compilación usar una devolución sin valor de un método declarado como devuelto por valor o por referencia (§15.6.1).

Se trata de un error en tiempo de compilación usar una devolución por referencia de un método declarado como devuelto sin valor o por referencia.

Se trata de un error en tiempo de compilación usar una devolución por valor de un método declarado como devuelto sin valor o por referencia.

Es un error en tiempo de compilación utilizar una devolución por referencia si la expresión no es una variable_reference o es una referencia a una variable cuyo ref-safe-context no es caller-context (§9.7.2).

Es un error en tiempo de compilación usar una devolución por referencia de un método declarado con el method_modifierasync.

Se dice que un miembro de función calcula un valor si es un método con una devolución por valor (§15.6.11), una devolución por valor de una propiedad o indexador, o un operador definido por el usuario. Los miembros de función que son devoluciones sin valor no calculan un valor y son métodos con el tipo de devolución efectivo void, establecen descriptores de acceso de propiedades e indexadores, añaden y eliminan descriptores de acceso de eventos, constructores de instancias, constructores estáticos y finalizadores. Los miembros de función que son devoluciones por referencia no calculan un valor.

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

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

Es un error en tiempo de compilación que una instrucción return aparezca en un bloque finally (§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 retorno por referencia, la expresión se evalúa y el resultado debe ser clasificado 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 return está encerrada por uno o más bloques try o catch con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado los bloques finally de todas las try instrucciones 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.14.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 instrucción throw produce una excepción.

throw_statement
    : 'throw' expression? ';'
    ;

Una instrucción throw 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.Exception, y el resultado de evaluar la expresión se convierte en System.Exception antes de lanzarse. Si el resultado de la conversión es null, se produce una System.NullReferenceException excepción en su lugar.

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

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 cláusula catch en una instrucción envolvente try que esté preparada para manejar 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 lanzamiento 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 instrucción try que incluye el punto de inicio. 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 bloque try de S incluye el punto de inicio y si S tiene una o varias cláusulas catch, estas cláusulas catch se examinan en orden de aparición 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 T de excepción) se considera una coincidencia, de modo que el tipo en tiempo de ejecución de E deriva de T. 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 bloque try o un bloque catch de S incluye el punto de inicio y si S tiene un bloque finally, el control se transfiere al bloque finally. 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 quien llama a la función con un punto de excepción correspondiente a la instrucción desde la que se invocó el miembro de la función.

    • Si la función actual es asincrónica y devuelve tareas, la excepción se registra en la tarea devuelta, que se coloca en un estado erróneo o cancelado, tal como se describe en §15.14.3.

    • Si la función actual es asíncrona y devuelve void, se notificará al contexto de sincronización del subproceso actual como se describe en §15.14.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 del bloque catch, la variable de excepción representa la excepción que se está manejando 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 en el bloque catch.

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 instrucción try solo puede tener una cláusula general catch y, si hay una, será la última cláusula de la instrucción catch.

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, es un error de compilación que una cláusula posterior catch de la misma try instrucción especifique un tipo que sea el mismo o derivado 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 relanza.

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 relanza 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 hubiera lanzado e en lugar de relanzar 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 que una instrucción break, continue o goto transfiera el control fuera de un bloque finally. Cuando se produce una instrucción break, continue o goto en un bloque finally, el objetivo de la instrucción estará dentro del mismo bloque finally o, de lo contrario, se producirá un error en tiempo de compilación.

Es un error en tiempo de compilación que una instrucción return ocurra en un bloque finally.

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 un finally bloque, el control se transfiere al punto final de la instrucción try.

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 especificador_de_excepción y cualquier filtro_de_excepción es verdadero. Una catch cláusula sin un exception_specifier coincide con cualquier 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 un finally bloque, el control se transfiere al punto final de la instrucción try. 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 circundante 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 circundante 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 al alcanzar el punto final del bloque finally no se propaga una excepción, el control se transfiere al punto final de la instrucción try.

Si se produce una excepción durante la ejecución de un bloque finally y no se detecta dentro del mismo bloque finally, 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 que encierran catch, ejecutando los filtros de excepción. A continuación, la cláusula finally en Method se ejecuta antes de que el control se transfiera a la cláusula envolvente coincidente catch. La salida resultante es:

Filter
Finally
Catch

ejemplo final

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

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

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

El punto final de una try instrucción es alcanzable si ambas de las siguientes condiciones son verdaderas:

  • 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 checked y unchecked

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

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 boxing implícita (§10.2.9) para la expresión de una instrucción lock y, por tanto, es un error de tiempo de compilación que la expresión denote un valor de un value_type.

Una instrucción lock de la forma

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, el código que se ejecuta en otros subprocesos está bloqueado de obtener el cerrojo hasta que este se libere.

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 (IAsyncDisposable para secuencias asincrónicas), que incluye un único método sin parámetros denominado Dispose (DisposeAsync para secuencias asincrónicas). 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, entonces el tipo de local_variable_declaration deberá ser dynamic o un tipo que se pueda convertir implícitamente en System.IDisposable (IAsyncDisposable para secuencias asincrónicas). Si la forma de resource_acquisition es expresión , esta expresión se podrá convertir implícitamente en System.IDisposable (IAsyncDisposable para secuencias asincrónicas).

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 instrucción using se traduce en tres partes: adquisición, uso y eliminación. El uso del recurso está implícitamente encerrado en una instrucción try que incluye una cláusula finally. Esta cláusula finally elimina el recurso. Si se adquiere un null recurso, no se realiza ninguna llamada a Dispose (DisposeAsync para secuencias asincrónicas) 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) en IDisposable (IAsyncDisposable para secuencias asincrónicas) durante la adquisición para garantizar que la conversión se realice correctamente antes del uso y eliminación.

Una instrucción using de la forma

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 haga que ocurra 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 instrucción using de la forma:

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 interfaz IDisposable (IAsyncDisposable para secuencias asincrónicas) se usa como ResourceType. La variable resource no es accesible en, y es invisible para, la instrucción insertada.

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

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.15.5) o un objeto enumerable (§15.15.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 instrucción yield, como se describe a continuación.

  • Es un error en tiempo de compilación que una yield instrucción (de cualquiera de las formas) aparezca fuera de method_body, operator_body o accessor_body.
  • Es un error de compilación que una instrucción yield (de cualquiera de las formas) aparezca dentro de una función anónima.
  • Es un error en tiempo de compilación cuando una instrucción yield (de cualquiera de las formas) aparezca en la cláusula finally de una instrucción try.
  • Es un error de compilación que una instrucción yield return aparezca en alguna parte de una instrucción try 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) debe existir desde el tipo de expresión de la yield return instrucción al tipo de rendimiento (§15.15.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 finally asociados 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 yield break está incluida en uno o varios bloques try con bloques finally asociados, el control se transfiere inicialmente al bloque finally de la instrucción try más interna. Si y cuando llegue al punto final de un bloque finally, el control se transfiere al bloque finally de la instrucción try siguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado los bloques finally de todas las try instrucciones envolventes.
  • El control se devuelve al llamador del bloque de iteradores. Este es el MoveNext método o el Dispose 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.