Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 (§24.2) y fixed_statement (§24.7) solo están disponibles en código no seguro (§24).
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
ifinstrucción requiere un embedded_statement en lugar de una instrucción para suiframa. Si se permitía este código, la variableise declararía, pero nunca se podría usar. Tenga en cuenta, sin embargo, que al colocar la declaración deien 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.25) 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
ifinstrucció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 valorfalse, se considera que la invocaciónConsole.WriteLinees inaccesible. Sin embargo, siise cambia para que sea una variable localvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }la
Console.WriteLineinvocació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.WriteLinese determina de la siguiente manera:
- La primera
Console.WriteLineinstrucción de expresión es accesible porque el bloque delFmétodo es accesible (§13.3).- El punto final de la primera
Console.WriteLineinstrucción de expresión es accesible porque esa instrucción es accesible (§13.7 y §13.3).- La
ifinstrucción es accesible porque el punto final de la primeraConsole.WriteLineinstrucción de expresión es accesible (§13.7 y §13.3).- La segunda
Console.WriteLineinstrucción de expresión es accesible porque la expresión booleana de laifinstrucción no tiene el valorfalseconstante .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
switchno 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 unabreakinstrucció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
returninstrucció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
returnaparezca en un bloque de iterador, pero se permiten las instruccionesyield return. - Es un error en tiempo de compilación para que un bloque de iterador contenga un contexto no seguro (§24.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
gotoaccesible .
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
whileinstrucció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
gotolas 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
varen el ámbito y la entrada coincide con implicitly_typed_local_variable_declaration, se elige esta opción. - De lo contrario, si un tipo denominado
varestá 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 un local_variable_declaration que no se produce como un 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 itselfejemplo 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 explicitly_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.23.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 inicialización variable_reference tendrá un tipo de tipo y cumplirá los mismos requisitos que para una asignación de referencia (§12.23.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.25) 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 (§24.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 (§24.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
thisygotoreflejan las reglas de las funciones anónimas en §12.21.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
Les accesible aunque el punto inicial deLno sea accesible. Dado que el punto inicial deLno es accesible, la instrucción que sigue al punto de conexión deLno 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ícitathis, ni a variables locales, parámetros o funciones locales no estáticas desde el ámbito envolvente. Sin embargo, todas estas se permiten en unanameof()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 + yyx == 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
ifdel formularioif (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 boolean_expression (§12.26).
- 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 laifinstrucción. - Si la expresión booleana produce
falsey si hay unaelseparte 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 laifinstrucción. - Si la expresión booleana produce
falsey si unaelseparte no está presente, el control se transfiere al punto final de laifinstrucció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 conmutador asociada que corresponde al valor del selector_expression del conmutador.
switch_statement
: 'switch' selector_expression switch_block
;
selector_expression
: '(' expression ')'
| tuple_expression
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' null_coalescing_expression
;
Un switch_statement consta de la palabra clave switch, seguido de una expresión tuple_expression o paréntesis (cada una de las cuales se denomina selector_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 case tiene un patrón asociado (§11) con el que se prueba el valor del selector_expression del modificador. 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.
Nota: Para mayor comodidad, los paréntesis de switch_statement se pueden omitir cuando el selector_expression es un tuple_expression. Por ejemplo,
switch ((a, b)) …puede escribirse comoswitch (a, b) …. nota final
El tipo de regulación de una switch instrucción se establece mediante el selector_expression del modificador.
- Si el tipo del selector_expression del modificador es
sbyte, ,shortbyte,intulonglongushortcharbooluint,stringo un enum_type, o si es el tipo de valor que acepta valores NULL correspondiente a uno de estos tipos, ese es el tipo de gobierno de laswitchinstrucción. - De lo contrario, si existe exactamente una conversión implícita definida por el usuario del tipo del selector_expression del modificador a uno de los siguientes tipos de gobierno posibles:
sbyte,byte,short,longintushortulongcharuintstringo , un tipo de valor que acepta valores NULL correspondiente a uno de esos tipos, el tipo convertido es el tipo de gobierno de laswitchinstrucción. - De lo contrario, el tipo de gobernanza de la
switchinstrucción es el tipo del selector_expression del modificador. 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:
- El selector_expression del modificador se evalúa y se convierte en el tipo de gobierno.
- El control se transfiere según el valor del selector_expression del conmutador convertido:
- El primer patrón léxico del conjunto de etiquetas de
casela mismaswitchinstrucción que coincide con el valor de la selector_expression del modificador y para el que la expresión de protección está ausente o se evalúa como true, hace que el control se transfiera a la lista de instrucciones siguiendo la etiqueta coincidentecase. - De lo contrario, si hay una
defaultetiqueta presente, el control se transfiere a la lista de instrucciones después de ladefaultetiqueta. - De lo contrario, el control se transfiere al destino final de la instrucción
switch.
- El primer patrón léxico del conjunto de etiquetas de
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 caseogoto 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:ydefault: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
breakse omiten accidentalmente. Por ejemplo, las secciones de la instrucciónswitchanterior 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 caseogoto 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 unawhileinstrucción controlada por la expresióntruebooleana nunca alcanza su punto final. Del mismo modo, unathrowdeclaración oreturnsiempre 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
switchinstrucción puede ser el tipostring. 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.14.8), la
switchinstrucción distingue mayúsculas de minúsculas y ejecutará una sección de conmutador determinada solo si la cadena de selector_expression del modificador coincide exactamente con unacaseconstante de etiqueta. nota final
Cuando el tipo de gobernanza de una switch instrucción es string o un tipo de valor que acepta valores NULL, el valor null se permite como una case constante de etiqueta.
Los statement_listde un switch_block pueden contener instrucciones de declaración (§13.6). El ámbito de una variable local o constante declarada en un bloque switch es el bloque switch.
Se puede acceder a una etiqueta switch si se cumple al menos una de las siguientes opciones:
- El selector_expression del modificador es un valor constante y cualquiera de los dos
- la etiqueta es un
casecuyo 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
defaulty 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 etiqueta es un
- El selector_expression del modificador no es un valor constante y tampoco
- la etiqueta es
casesin restricción o cuenta con una restricción cuyo valor no es la constante "false"; o - es una etiqueta
defaulty- 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.
- la etiqueta es
- Se hace referencia a la etiqueta switch mediante una instrucción
goto caseogoto defaultaccesible.
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
switchcontiene una instrucciónbreakaccesible que termina la instrucciónswitch. - Ninguna etiqueta
defaultestá presente y tampoco- El selector_expression del modificador es un valor no constante y el conjunto de patrones que aparecen entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true, no es exhaustivo (§11.4) para el tipo de regulación del modificador.
- El selector_expression del modificador es un valor no constante de un tipo que acepta valores NULL y ningún patrón aparece entre los casos de la instrucción switch que no tienen guardias o tienen guardias cuyo valor es la constante true coincidiría con el valor
null. - El selector_expression del modificador es un valor constante y ninguna
caseetiqueta sin 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
whenclá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 boolean_expression (§12.26).
- 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 unacontinueinstrucción), el control se transfiere al principio de lawhileinstrucción. - Si la expresión booleana produce
false, el control se transfiere al punto final de lawhileinstrucció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
whilecontiene una instrucciónbreakaccesible que termina la instrucciónwhile. - La
whileinstrucción es accesible y la expresión booleana no tiene el valortrueconstante .
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 alcanza el punto final de la instrucción insertada (posiblemente desde la ejecución de una
continueinstrucción), se evalúa el boolean_expression (§12.26). Si la expresión booleana producetrue, el control se transfiere al principio de ladoinstrucción . De lo contrario, el control se transfiere al destino final de la instruccióndo.
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
docontiene una instrucciónbreakaccesible que termina la instruccióndo. - 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.26).
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 unacontinueinstrucció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 laforsentencia.
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
forinstrucción es accesible y no hay ninguna for_condition presente. - La
forinstrucción es accesible y hay una for_condition presente y no tiene el valor constantefalse.
El punto final de una for instrucción es accesible si se cumple al menos uno de los siguientes elementos:
- La instrucción
forcontiene una instrucciónbreakaccesible que termina la instrucciónfor. - La
forinstrucción es accesible y hay una for_condition presente y no tiene el valor constantetrue.
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 (C), el tipo de enumerador (E) y el tipo de iteración (To ref Tref readonly T) de la expresión.
La determinación es similar para las versiones sincrónicas y asincrónicas. Las distintas interfaces con diferentes métodos y tipos de valor devuelto distinguen las versiones sincrónicas y asincrónicas. El proceso general continúa de la siguiente manera. Los nombres dentro de '«' y '»' son marcadores de posición para los nombres reales para los iteradores sincrónicos y asincrónicos. Los tipos permitidos para «GetEnumerator», «MoveNext», «IEnumerable»T, «IEnumerator»< T> y cualquier otra distinción se detallan en < para una instrucción sincrónica > y en §13.9.5.3 para una instrucción asincrónicaforeach.foreach
- Determine si el tipo
Xde expresión tiene un método «GetEnumerator» adecuado:- Realice la búsqueda de miembros en el tipo
Xcon el identificador «GetEnumerator» y ningún argumento 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 en el paso 2. 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
Ede valor devuelto del método «GetEnumerator» no es una clase, estructura o tipo de interfaz, genere un error y no realice más pasos. - Realice la búsqueda de miembros con
Eel identificadorCurrenty ningún argumento 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, genere un error y no realice más pasos. - Realice la búsqueda de miembros con
Eel identificador «MoveNext» y ningún argumento 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, genere un error y no realice más pasos. - Realice la resolución de sobrecargas en el grupo de métodos con una lista de argumentos vacía. Si la resolución de sobrecarga da como resultado: no hay métodos aplicables; ambigüedad; o un único método mejor, pero ese método es estático, o no público, o su tipo de valor devuelto no es un tipo de valor devuelto permitido; después, genere un error y no realice más pasos.
- El tipo de colección es
X, el tipo de enumerador esEy el tipo de iteración es el tipo de laCurrentpropiedad .
- Realice la búsqueda de miembros en el tipo
- De lo contrario, compruebe si hay una interfaz enumerable:
- Si entre todos los tipos
Tᵢpara los que hay una conversión implícita deXa «IEnumerable»<Ti>, hay un tipoTúnico tal queTnodynamices y para el restoTᵢhay una conversión implícita de «IEnumerable»T< a «IEnumerable»>Ti<, entonces 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 esT. - De lo contrario, si hay más de un tipo de este tipo
T, genere un error y no realice más pasos.
- Si entre todos los tipos
Nota: Si la expresión tiene el valor
null, se lanza una excepciónSystem.NullReferenceExceptionen tiempo de ejecución. nota final
Una implementación puede implementar una foreach_statement determinada de forma diferente; Por ejemplo, por motivos de rendimiento, siempre que el comportamiento sea coherente con las expansiones descritas en §13.9.5.2 y §13.9.5.3.
13.9.5.2 foreach sincrónico
Una sincrónica foreach no incluye la await palabra clave antes de la foreach palabra clave . La determinación del tipo de colección, el tipo de enumeración y el tipo de iteración continúa como se describe en §13.9.5.1, donde:
- «GetEnumerator» es un
GetEnumeratormétodo. - «MoveNext» es un
MoveNextmétodo con unbooltipo de valor devuelto. - «IEnumerable»<T> es la
System.Collections.Generic.IEnumerable<T>interfaz. - «IEnumerator»<T> es la
System.Collections.Generic.IEnumerator<T>interfaz.
Además, se realizan las siguientes modificaciones en los pasos descritos en §13.9.5.1:
Antes del proceso descrito en §13.9.5.1, se realizan los pasos siguientes:
- Si el tipo
Xde expresión es un tipo de matriz, hay una conversión de referencia implícita desdeXa laIEnumerable<T>interfaz dondeTes el tipo de elemento de la matrizX(§17.2.3). - Si el tipo
Xde expresión esdynamicentonces hay una conversión implícita de expresión a laIEnumerableinterfaz (§10.2.10). El tipo de colección es laIEnumerableinterfaz y el tipo de enumerador es laIEnumeratorinterfaz. Si elvaridentificador se proporciona como el local_variable_type , el tipo de iteración esdynamic, de lo contrario, esobject.
Si el proceso de §13.9.5.1 se completa sin generar un único tipo de colección, tipo de enumerador y tipo de iteración, se realizan los pasos siguientes:
- Si hay una conversión implícita de a la
Xinterfaz, el tipo de colección es esta interfaz, el tipo de enumerador es la interfazSystem.Collections.IEnumerabley el tipo de iteración esSystem.Collections.IEnumerator.object - De lo contrario, se produce un error y no se realizan pasos adicionales.
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 se volverá a reasignar (§12.23.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.21.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
ven el formato expandido se declarase fuera delwhilebucle, se compartiría entre todas las iteraciones y su valor después delforbucle sería el valor final,13, que es lo que la invocación defimprimiría. En su lugar, dado que cada iteración tiene su propia variablev, la capturada porfen la primera iteración seguirá manteniendo el valor7, que es lo que se imprimirá. (Tenga en cuenta que las versiones anteriores de C# declararonvfuera del buclewhile).ejemplo final
El cuerpo del finally bloque se construye según los pasos siguientes:
Si hay una conversión implícita de
Ea laSystem.IDisposableinterfaz,Si
Ees un tipo de valor que no acepta valores NULL, lafinallycláusula se expande al equivalente semántico de:finally { ((System.IDisposable)e).Dispose(); }De lo contrario, la
finallycláusula se expande al equivalente semántico de:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }excepto que si
Ees un tipo de valor o un parámetro de tipo instanciado como un tipo de valor, la conversión deeaSystem.IDisposableno hará que se produzca la encapsulación.
De lo contrario, si
Ees un tipo sellado, lafinallycláusula se expande a un bloque vacío:finally {}De lo contrario, la
finallyclá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.9ejemplo final
Ejemplo: En el ejemplo siguiente
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }el tipo de
nse deduce comoint, el tipo de iteración denumbers.ejemplo final
13.9.5.3 await foreach
Un foreach asincrónico usa la await foreach sintaxis . La determinación del tipo de colección, el tipo de enumeración y el tipo de iteración continúa como se describe en §13.9.5.1, donde:
- «GetEnumerator» es un método que tiene un
GetEnumeratorAsynctipo de valor devuelto que se puede esperar (§12.9.9.2). - «MoveNext» es un método que tiene un
MoveNextAsynctipo de valor devuelto que se puede esperar (§12.9.9.2) donde el await_expression se clasifica como unbool(§12.9.9.3). - «IEnumerable»<T> es la
System.Collections.Generic.IAsyncEnumerable<T>interfaz. - «IEnumerator»<T> es la
System.Collections.Generic.IAsyncEnumerator<T>interfaz.
Es un error para que el tipo de iteración de una await foreach instrucción sea una variable de referencia (§9.7).
Una instrucción await foreach de la forma
await foreach (T item in enumerable) «embedded_statement»
es semánticamente equivalente a:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
En el caso de que la expresión represente una expresión enumerable de llamada de método y uno de los parámetros se marca con el EnumeratorCancellationAttribute (§23.5.8) que CancellationToken se pasa al GetAsyncEnumerator método . Otros métodos de biblioteca pueden requerir que CancellationToken se pase a GetAsyncEnumerator. Cuando esos métodos forman parte de la expresión enumerable, los tokens se combinarán en un único token como si fuera por CreateLinkedTokenSource y su Token propiedad.
El cuerpo del finally bloque se construye según los pasos siguientes:
Si
Etiene un método accesibleDisposeAsync()en el que se puede esperar el tipo de valor devuelto (§12.9.9.2), lafinallycláusula se expande al equivalente semántico de:finally { await e.DisposeAsync(); }De lo contrario, si hay una conversión implícita de
Ea laSystem.IAsyncDisposableinterfaz yEes un tipo de valor que no acepta valores NULL, lafinallycláusula se expande al equivalente semántico de:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }excepto que si
Ees un tipo de valor o un parámetro de tipo instanciado como un tipo de valor, la conversión deeaSystem.IAsyncDisposableno hará que se produzca la encapsulación.De lo contrario, si
Ees un tipo y tiene unref structmétodo accesibleDispose(), lafinallycláusula se expande al equivalente semántico de:finally { e.Dispose(); }De lo contrario, si
Ees un tipo sellado, lafinallycláusula se expande a un bloque vacío:finally {}De lo contrario, la
finallycláusula se expande a:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
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.
Nota:
await foreachNo es necesario eliminar deeforma sincrónica si un mecanismo de eliminación asincrónica no está disponible. nota final
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
finallyasociados a dos instruccionestryse 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 breakejemplo 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
breaksale de uno o varios bloquestrycon bloquesfinallyasociados, el control se transfiere inicialmente al bloquefinallyde la instruccióntrymás interna. Si y cuando llegue al punto final de un bloquefinally, el control se transfiere al bloquefinallyde la instruccióntrysiguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado losfinallybloques de todas las instruccionestryintermedias. - 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
continuesale de uno o varios bloquestrycon bloquesfinallyasociados, el control se transfiere inicialmente al bloquefinallyde la instruccióntrymás interna. Si y cuando llegue al punto final de un bloquefinally, el control se transfiere al bloquefinallyde la instruccióntrysiguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado losfinallybloques de todas las instruccionestryintermedias. - 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
gotoinstrucción para transferir el control fuera de un ámbito anidado, pero no en un ámbito anidado. En el ejemploclass 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
gotopara 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
gotosale de uno o varios bloquestrycon bloquesfinallyasociados, el control se transfiere inicialmente al bloquefinallyde la instruccióntrymás interna. Si y cuando llegue al punto final de un bloquefinally, el control se transfiere al bloquefinallyde la instruccióntrysiguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado losfinallybloques de todas las instruccionestryintermedias. - 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 También se pueden usar instrucciones en el cuerpo de expresiones de función anónimas (§12.21) 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
returnestá encerrada por uno o más bloquestryocatchcon bloquesfinallyasociados, el control se transfiere inicialmente al bloquefinallyde la instruccióntrymás interna. Si y cuando llegue al punto final de un bloquefinally, el control se transfiere al bloquefinallyde la instruccióntrysiguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado los bloquesfinallyde todas lastryinstrucciones 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 (§22.4).
En el miembro de función actual, se examina cada instrucción
tryque incluye el punto de inicio. Para cada instrucciónS, empezando por la instrucción mástryinterna y terminando con la instrucción mástryexterna, se evalúan los pasos siguientes:- Si el bloque
trydeSincluye el punto de inicio y siStiene una o varias cláusulascatch, estas cláusulascatchse examinan en orden de aparición para buscar un controlador adecuado para la excepción. La primeracatchcláusula que especifica un tipoTde excepción (o un parámetro de tipo que, en tiempo de ejecución, denota un tipoTde excepción) se considera una coincidencia, de modo que el tipo en tiempo de ejecución deEderiva deT. 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 unacatchcláusula contiene un filtro de excepciones, esacatchcláusula se considera una coincidencia si el filtro de excepción se evalúa comotrue. Una cláusula generalcatch(§13.11) se considera una coincidencia para cualquier tipo de excepción. Si se encuentra una cláusula coincidentecatch, la propagación de excepciones se completa transfiriendo el control al bloque de esacatchcláusula. - De lo contrario, si el bloque
tryo un bloquecatchdeSincluye el punto de inicio y siStiene un bloquefinally, el control se transfiere al bloquefinally. Si el bloquefinallygenera otra excepción, finaliza el procesamiento de la excepción actual. De lo contrario, cuando el control alcanza el punto final delfinallybloque, se continúa el procesamiento de la excepción actual.
- Si el bloque
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 generalcatchpara detectar estas excepciones. Por lo tanto, una cláusula generalcatches semánticamente diferente de una que especifica el tipoSystem.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
Fdetecta 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: GSi el primer
catchbloque hubiera lanzadoeen lugar de relanzar la excepción actual, la salida generada sería la siguiente:Exception in F: G Exception in Main: Fejemplo 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 la evaluación de la boolean_expression de un exception_filter produce una excepción, esa excepción se detecta y el filtro de excepción se evalúa como false.
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
Methodproduce una excepción. La primera acción consiste en examinar las cláusulas que encierrancatch, ejecutando los filtros de excepción. A continuación, la cláusulafinallyenMethodse ejecuta antes de que el control se transfiera a la cláusula envolvente coincidentecatch. La salida resultante es:Filter Finally Catchejemplo 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
trybloque es accesible o se puede acceder al punto final de al menos uncatchbloque. - Si hay un
finallybloque presente, se puede acceder al punto final delfinallybloque.
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
13.14.1 General
La using instrucción obtiene uno o varios recursos, ejecuta una instrucción y, a continuación, elimina el recurso.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
Un tipo de System.IDisposableSystem.IAsyncDisposable recurso es una clase o una estructura que no es ref que implementa o ambas interfaces o , que incluye un único método sin parámetros denominado Dispose y/o DisposeAsync; o un struct ref que incluye un método denominado Dispose que tiene la misma firma que la declarada por System.IDisposable. El código que usa un recurso puede llamar a Dispose o DisposeAsync indicar que el recurso ya no es necesario.
Si la forma de resource_acquisition es local_variable_declaration , el tipo del local_variable_declaration será o dynamic un tipo de recurso. Si la forma de resource_acquisition es expresión , esta expresión tendrá un tipo de recurso. Si await está presente, el tipo de recurso implementará System.IAsyncDisposable. Un ref struct tipo no puede ser el tipo de recurso de una using instrucción con el await modificador .
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 la expresión de adquisición se evalúa como null, no se realiza ninguna llamada a Dispose (o DisposeAsync) y no se produce ninguna excepción. Si el recurso es de tipo dynamic , se convierte dinámicamente a través de una conversión dinámica implícita (§10.2.10) a IDisposable (o IAsyncDisposable) durante la adquisición para asegurarse de 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 posibles formulaciones. En el caso de los recursos de clase y de estructura no ref, 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 formulació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 formulación es
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
De lo contrario, la formulación es
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Para los recursos de estructura ref, la única formulación semánticamente equivalente es
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
En cualquier formulación, la resource variable es de solo lectura en la instrucción insertada y la d variable no está accesible en la instrucción insertada y es invisible para ella.
Una instrucción using de la forma:
using («expression») «statement»
tiene las mismas formulaciones posibles.
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
TextWriterclases yTextReaderimplementan laIDisposableinterfaz , el ejemplo puede usarusinginstrucciones para asegurarse de que el archivo subyacente está cerrado correctamente después de las operaciones de escritura o lectura.ejemplo final
Cuando ResourceType es un tipo de referencia que implementa IAsyncDisposable. Otras formulaciones para await using realizar sustituciones similares del método sincrónico Dispose al método asincrónico DisposeAsync . Una instrucción await using de la forma
await using (ResourceType resource = «expression» ) «statement»
es semánticamente equivalente a las formulaciones que se muestran a continuación con IAsyncDisposable en lugar de IDisposable, DisposeAsync en lugar de Dispose, y el Task devuelto de DisposeAsync es awaited:
await using (ResourceType resource = «expression» ) «statement»
es semánticamente equivalente a
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Nota: Las instrucciones de salto (§13.10) de la embedded_statement deben ajustarse a la forma expandida de la
usinginstrucción. nota final
13.14.2 Uso de la declaración
Una variante sintáctica de la instrucción using es una declaración using.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Una declaración using tiene la misma semántica que y se puede reescribir como la forma correspondiente de adquisición de recursos de la instrucción using (§13.14.1), como se indica a continuación:
using «local_variable_type» «local_variable_declarators»
// statements
es semánticamente equivalente a
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
y
await using «local_variable_type» «local_variable_declarators»
// statements
es semánticamente equivalente a
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
La duración de las variables declaradas en un non_ref_local_variable_declaration se extiende hasta el final del ámbito en el que se declaran. A continuación, esas variables se eliminan en el orden inverso en el que se declaran.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Una declaración using no aparecerá directamente dentro de un switch_label, sino que, en su lugar, puede estar dentro de un bloque dentro de un switch_label.
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
yieldinstrucció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áusulafinallyde una instruccióntry. - Es un error de compilación que una instrucción
yield returnaparezca en alguna parte de una instruccióntryque contenga cualquier catch_clauses.
Ejemplo: en el ejemplo siguiente se muestran algunos usos válidos e no válidos de
yieldinstrucciones.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
Currentpropiedad del objeto enumerador. - Se suspende la ejecución del bloque de iterador. Si la
yield returninstrucción está dentro de uno o variostrybloques, los bloquesfinallyasociados no se ejecutan en este momento. - El
MoveNextmétodo del objeto enumerador vuelvetruea 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 breakestá incluida en uno o varios bloquestrycon bloquesfinallyasociados, el control se transfiere inicialmente al bloquefinallyde la instruccióntrymás interna. Si y cuando llegue al punto final de un bloquefinally, el control se transfiere al bloquefinallyde la instruccióntrysiguiente que lo envuelve. Este proceso se repite hasta que se han ejecutado los bloquesfinallyde todas lastryinstrucciones envolventes. - El control se devuelve al llamador del bloque de iteradores. Este es el
MoveNextmétodo o elDisposemé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.
ECMA C# draft specification