Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
13.1 Geral
O C# fornece uma variedade de instruções.
Nota: A maioria dessas instruções será familiar para desenvolvedores que programaram em C e 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) e fixed_statement (§24.7) só estão disponíveis em código não seguro (§24).
O embedded_statement nonterminal é usado para instruções que aparecem em outras instruções. O uso de embedded_statement instrução em vez de exclui o uso de instruções de declaração e instruções rotuladas nesses contextos.
Exemplo: O código
void F(bool b) { if (b) int i = 44; }resulta em um erro de tempo de compilação porque uma
ifinstrução requer um embedded_statement em vez de uma instrução para suaiframificação. Se esse código fosse permitido, a variáveliseria declarada, mas nunca poderia ser usada. Observe, no entanto, que ao colocaria declaração de 's em um bloco, o exemplo é válido.exemplo de fim
13.2 Pontos finais e acessibilidade
Cada declaração tem um ponto final. Em termos intuitivos, o ponto final de uma declaração é o local que segue imediatamente a declaração. As regras de execução para instruções compostas (instruções que contêm instruções incorporadas) especificam a ação que é executada quando o controle atinge o ponto final de uma instrução incorporada.
Exemplo: quando o controle atinge o ponto final de uma instrução em um bloco, o controle é transferido para a próxima instrução no bloco. exemplo de fim
Se uma declaração puder ser alcançada por execução, a declaração é considerada alcançável. Por outro lado, se não houver possibilidade de que uma instrução seja executada, a instrução é considerada inacessível.
Exemplo: no código a seguir
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }a segunda invocação de Console.WriteLine é inacessível porque não há possibilidade de que a instrução seja executada.
exemplo de fim
Um aviso será relatado se uma instrução diferente de throw_statement, block ou empty_statement estiver inacessível. Especificamente, não é um erro que uma instrução seja inacessível.
Observação: para determinar se uma instrução específica ou ponto de extremidade é acessível, um compilador executa a análise de fluxo de acordo com as regras de acessibilidade definidas para cada instrução. A análise de fluxo leva em conta os valores de expressões constantes (§12.25) que controlam o comportamento das instruções, mas os valores possíveis de expressões não constantes não são considerados. Em outras palavras, para fins de análise de fluxo de controle, uma expressão não constante de um determinado tipo é considerada como tendo qualquer valor possível desse tipo.
No exemplo
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }a expressão booleana da
ifinstrução é uma expressão constante porque ambos os operandos do==operador são constantes. Como a expressão constante é avaliada em tempo de compilação, produzindo o valorfalse, aConsole.WriteLineinvocação é considerada inacessível. No entanto, ifié alterado para ser uma variável localvoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }A
Console.WriteLineinvocação é considerada alcançável, embora, na realidade, nunca seja executada.nota final
O bloco de um membro da função ou de uma função anônima é sempre considerado alcançável. Ao avaliar sucessivamente as regras de acessibilidade de cada instrução em um bloco, a acessibilidade de qualquer instrução pode ser determinada.
Exemplo: no código a seguir
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }A acessibilidade do segundo
Console.WriteLineé determinada da seguinte forma:
- A primeira
Console.WriteLineinstrução de expressão é alcançável porque o bloco doFmétodo é alcançável (§13.3).- O ponto final da instrução da primeira
Console.WriteLineexpressão é alcançável porque essa instrução é alcançável (§13.7 e §13.3).- A
ifinstrução é alcançável porque o ponto final da instrução da primeiraConsole.WriteLineexpressão é alcançável (§13.7 e §13.3).- A segunda
Console.WriteLineinstrução de expressão pode ser acessada porque a expressão booleana daifinstrução não tem o valorfalseconstante .exemplo de fim
Há duas situações em que é um erro em tempo de compilação que o ponto final de uma instrução seja alcançável:
Como a
switchinstrução não permite que uma seção switch "caia" para a próxima seção switch, é um erro em tempo de compilação que o ponto final da lista de instruções de uma seção switch seja alcançável. Se esse erro ocorrer, normalmente é uma indicação de que umabreakinstrução está ausente.É um erro de tempo de compilação para o ponto final do bloco de um membro da função ou uma função anônima que calcula um valor a ser alcançável. Se esse erro ocorrer, normalmente é uma indicação de que uma
returninstrução está ausente (§13.10.5).
13.3 Blocos
13.3.1 Geral
Um bloco permite a produção de várias instruções em contextos nos quais uma única instrução é permitida.
block
: '{' statement_list? '}'
;
Um bloco consiste em um statement_list opcional (§13.3.2), entre chaves. Se a lista de instruções for omitida, o bloco será considerado vazio.
Um bloco pode conter instruções de declaração (§13.6). O escopo de uma variável local ou constante declarada em um bloco é o bloco.
Um bloco é executado da seguinte forma:
- Se o bloco estiver vazio, o controle será transferido para o ponto final do bloco.
- Se o bloco não estiver vazio, o controle será transferido para a lista de instruções. Quando e se o controle atingir o ponto final da lista de instruções, o controle será transferido para o ponto final do bloco.
A lista de instruções de um bloco pode ser acessada se o próprio bloco estiver acessível.
O ponto final de um bloco é alcançável se o bloco estiver vazio ou se o ponto final da lista de instruções estiver acessível.
Um bloco que contém uma ou mais yield instruções (§13.15) é chamado de bloco iterador. Os blocos de iterador são usados para implementar membros de função como iteradores (§15.15). Algumas restrições adicionais se aplicam aos blocos do iterador:
- É um erro de tempo de compilação para uma
returninstrução aparecer em um bloco de iterador (masyield returninstruções são permitidas). - É um erro de tempo de compilação para um bloco de iterador conter um contexto não seguro (§24.2). Um bloco iterador sempre define um contexto seguro, mesmo quando sua declaração está aninhada em um contexto não seguro.
13.3.2 Listas de declarações
Uma lista de instruções consiste em uma ou mais instruções escritas em sequência. As listas de instruções ocorrem no bloco s (§13.3) e no switch_blocks (§13.8.3).
statement_list
: statement+
;
Uma lista de instruções é executada transferindo o controle para a primeira instrução. Quando e se o controle atingir o ponto final de uma instrução, o controle será transferido para a próxima instrução. Quando e se o controle atingir o ponto final da última instrução, o controle será transferido para o ponto final da lista de instruções.
Uma instrução em uma lista de instruções pode ser acessada se pelo menos uma das seguintes opções for verdadeira:
- A instrução é a primeira instrução e a própria lista de instruções pode ser acessada.
- O ponto final da instrução anterior é alcançável.
- A instrução é uma instrução rotulada e o rótulo é referenciado por uma instrução alcançável
goto.
O ponto final de uma lista de instruções é alcançável se o ponto final da última instrução na lista for alcançável.
13.4 A declaração vazia
Um empty_statement não faz nada.
empty_statement
: ';'
;
Uma instrução vazia é usada quando não há operações a serem executadas em um contexto em que uma instrução é necessária.
A execução de uma instrução vazia simplesmente transfere o controle para o ponto final da instrução. Assim, o ponto final de uma instrução vazia é alcançável se a instrução vazia for alcançável.
Exemplo: Uma instrução vazia pode ser usada ao escrever uma
whileinstrução com um corpo nulo:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }Além disso, uma instrução vazia pode ser usada para declarar um rótulo logo antes do fechamento "
}" de um bloco:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }exemplo de fim
13.5 Instruções rotuladas
Um labeled_statement permite que uma instrução seja prefixada por um rótulo. Instruções rotuladas são permitidas em blocos, mas não são permitidas como instruções incorporadas.
labeled_statement
: identifier ':' statement
;
Uma instrução rotulada declara um rótulo com o nome dado pelo identificador. O escopo de um rótulo é todo o bloco no qual o rótulo é declarado, incluindo todos os blocos aninhados. É um erro de tempo de compilação que dois rótulos com o mesmo nome tenham escopos sobrepostos.
Um rótulo pode ser referenciado a partir de goto instruções (§13.10.4) dentro do escopo do rótulo.
Observação: isso significa que
gotoas instruções podem transferir o controle dentro e fora dos blocos, mas nunca em blocos. nota final
Os rótulos têm seu próprio espaço de declaração e não interferem em outros identificadores.
Exemplo: O exemplo
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }é válido e usa o nome x como parâmetro e rótulo.
exemplo de fim
A execução de uma instrução rotulada corresponde exatamente à execução da instrução após o rótulo.
Além da acessibilidade fornecida pelo fluxo normal de controle, uma instrução rotulada poderá ser acessada se o rótulo for referenciado por uma instrução alcançável goto , a menos que a goto instrução esteja dentro do try bloco ou de um catch bloco de um try_statement que inclua um finally bloco cujo ponto final seja inalcançável e a instrução rotulada esteja fora do try_statement.
13.6 Declarações de declaração
13.6.1 Geral
Um declaration_statement declara uma ou mais variáveis locais, uma ou mais constantes locais ou uma função local. As declarações de declaração são permitidas em blocos e blocos de switch, mas não são permitidas como instruções incorporadas.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Uma variável local é declarada usando um local_variable_declaration (§13.6.2). Uma constante local é declarada usando um local_constant_declaration (§13.6.3). Uma função local é declarada usando um local_function_declaration (§13.6.4).
Os nomes declarados são introduzidos no espaço de declaração delimitador mais próximo (§7.3).
13.6.2 Declarações de variáveis locais
13.6.2.1 Geral
Um local_variable_declaration declara uma ou mais variáveis locais.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
As declarações digitadas implicitamente contêm a palavra-chave contextual (§6.4.4), var resultando em uma ambiguidade sintática entre as três categorias, que é resolvida da seguinte maneira:
- Se não houver nenhum tipo nomeado
varno escopo e a entrada corresponder a implicitly_typed_local_variable_declaration ela será escolhida; - Caso contrário, se um tipo nomeado
varestiver no escopo, implicitly_typed_local_variable_declaration não será considerado como uma correspondência possível.
Dentro de um local_variable_declaration cada variável é introduzida por um declarador, que é uma das variáveis locais implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator ou ref_local_variable_declarator para variáveis locais implicitamente tipadas, explicitamente tipadas e ref, respectivamente. O declarador define o nome (identificador) e o valor inicial, se houver, da variável introduzida.
Se houver vários declaradores em uma declaração, eles serão processados, incluindo quaisquer expressões de inicialização, da esquerda para a direita (§9.4.4.5).
Observação: para um local_variable_declaration não ocorrendo como um for_initializer (§13.9.4) ou resource_acquisition (§13.14) essa ordem da esquerda para a direita é equivalente a cada declarador estar dentro de um local_variable_declaration separado. Por exemplo:
void F() { int x = 1, y, z = x * 2; }é equivalente a:
void F() { int x = 1; int y; int z = x * 2; }nota final
O valor de uma variável local é obtido em uma expressão usando um simple_name (§12.8.4). Uma variável local deve ser definitivamente atribuída (§9.4) em cada local onde seu valor é obtido. Cada variável local introduzida por um local_variable_declaration é inicialmente não atribuída (§9.4.3). Se um declarador tiver uma expressão de inicialização, a variável local introduzida será classificada como atribuída no final do declarador (§9.4.4.5).
O escopo de uma variável local introduzida por um local_variable_declaration é definido da seguinte forma (§7.7):
- Se a declaração ocorrer como um for_initializer o escopo será o for_initializer, for_condition, for_iterator e embedded_statement (§13.9.4);
- Se a declaração ocorrer como um resource_acquisition o escopo será o bloco mais externo da expansão semanticamente equivalente do using_statement (§13.14);
- Caso contrário, o escopo é o bloco no qual a declaração ocorre.
É um erro referir-se a uma variável local pelo nome em uma posição textual que precede seu declarador ou em qualquer expressão de inicialização dentro de seu declarador. Dentro do escopo de uma variável local, é um erro de tempo de compilação declarar outra variável local, função local ou constante com o mesmo nome.
O ref-safe-context (§9.7.2) de uma variável local ref é o ref-safe-context de seu variable_reference inicializador. O ref-safe-context de variáveis locais não-ref é declaration-block.
13.6.2.2 Declarações de variáveis locais digitadas implicitamente
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
;
Um implicitly_typed_local_variable_declaration introduz uma única variável local, o identificador. A expressão ou variable_reference deve ter um tipo de tempo de compilação, T. A primeira alternativa declara uma variável com um valor inicial de expressão; seu tipo é T? quando T é um tipo de referência não anulável, caso contrário, seu tipo é T. A segunda alternativa declara uma variável ref com um valor inicial de ref variable_reference; seu tipo é when ref T? é um tipo de referência não anulável, caso contrário, seu tipo é T.ref T (ref_kind é descrito em §15.6.1.)
Exemplo:
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;As declarações de variáveis locais digitadas implicitamente acima são precisamente equivalentes às seguintes declarações explicitamente tipadas:
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;A seguir estão declarações de variáveis locais digitadas implicitamente incorretas:
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 itselfexemplo de fim
13.6.2.3 Declarações de variáveis locais tipadas explicitamente
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
;
Um explicitly_typed_local_variable_declaration apresenta uma ou mais variáveis locais com o tipo especificado.
Se um local_variable_initializer estiver presente, seu tipo será apropriado de acordo com as regras de atribuição simples (§12.23.2) ou inicialização da matriz (§17.7) e seu valor será atribuído como o valor inicial da variável.
13.6.2.4 Declarações de variáveis locais ref digitadas explicitamente
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
;
A inicialização variable_reference deve ter tipo de tipo e atender aos mesmos requisitos de uma atribuição ref (§12.23.3).
Se ref_kind for ref readonly, os identificadores que estão sendo declarados serão referências a variáveis tratadas como somente leitura. Caso contrário, se ref_kind for ref, os identificadores que estão sendo declarados serão referências a variáveis que devem ser graváveis.
É um erro de tempo de compilação declarar uma variável local ref, ou uma variável de um tipo ref struct, dentro de um método declarado com o method_modifierasync, ou dentro de um iterador (§15.15).
13.6.3 Declarações constantes locais
Um local_constant_declaration declara uma ou mais constantes locais.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
O tipo de um local_constant_declaration especifica o tipo das constantes introduzidas pela declaração. O tipo é seguido por uma lista de constant_declarators, cada uma das quais introduz uma nova constante. Um constant_declarator consiste em um identificador que nomeia a constante, seguido por um token "=", seguido por um constant_expression (§12,25) que fornece o valor da constante.
O tipo e a constant_expression de uma declaração constante local devem seguir as mesmas regras de uma declaração de membro constante (§15.4).
O valor de uma constante local é obtido em uma expressão usando um simple_name (§12.8.4).
O escopo de uma constante local é o bloco no qual a declaração ocorre. É um erro referir-se a uma constante local em uma posição textual que precede o final de sua constant_declarator.
Uma declaração de constante local que declara várias constantes é equivalente a várias declarações de constantes únicas com o mesmo tipo.
13.6.4 Declarações de função local
Um local_function_declaration declara uma função 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: Ao reconhecer um local_function_body se forem aplicáveis as alternativas null_conditional_invocation_expression e de expressão , deve ser escolhida a primeira. (§15.6.1)
Exemplo: há dois casos de uso comuns para funções locais: métodos iteradores e métodos assíncronos. Em métodos iteradores, as exceções são observadas apenas ao chamar o código que enumera a sequência retornada. Em métodos assíncronos, todas as exceções só são observadas quando a Tarefa retornada é aguardada. O seguinte exemplo demonstra a validação de parâmetro de separação da implementação do iterador usando uma função 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; } } }exemplo de fim
A menos que especificado de outra forma abaixo, a semântica de todos os elementos gramaticais é a mesma que para method_declaration (§15.6.1), lida no contexto de uma função local em vez de um método.
O identificador de um local_function_declaration deve ser único em seu escopo de bloco declarado, incluindo quaisquer espaços de declaração de variável local delimitados. Uma consequência disso é que local_function_declarationsobrecarregados não são permitidos.
Um local_function_declaration pode incluir um asyncmodificador (§15.14) e um unsafemodificador (§24.1). Se a declaração incluir o async modificador, o tipo de retorno será void ou um «TaskType» tipo (§15.14.1). Se a declaração incluir o static modificador, a função será uma função local estática; caso contrário, será uma função local não estática. É um erro de tempo de compilação para type_parameter_list ou parameter_list conter atributos. Se a função local for declarada em um contexto não seguro (§24.2), a função local poderá incluir código não seguro, mesmo que a declaração de função local não inclua o unsafe modificador.
Uma função local é declarada no escopo do bloco. Uma função local não estática pode capturar variáveis do escopo delimitador, enquanto uma função local estática não deve (portanto, ela não tem acesso a locais delimitadores, parâmetros, funções locais não estáticas ou this). É um erro de tempo de compilação se uma variável capturada for lida pelo corpo de uma função local não estática, mas não for definitivamente atribuída antes de cada chamada para a função. Um compilador deve determinar quais variáveis são definitivamente atribuídas no retorno (§9.4.4.33).
Quando o tipo de é um tipo struct this , é um erro de tempo de compilação para o corpo de uma função local acessar this. Isso é verdadeiro se o acesso for explícito (como em this.x) ou implícito (como em x where x is an instance member of the struct). Essa regra proíbe apenas esse acesso e não afeta se a pesquisa de membro resulta em um membro do struct.
É um erro de tempo de compilação para o corpo da função local conter uma goto instrução, uma break instrução ou uma continue instrução cujo destino está fora do corpo da função local.
Observação: as regras acima para
thisegotoespelham as regras para funções anônimas em §12.21.3. nota final
Uma função local pode ser chamada a partir de um ponto lexical antes de sua declaração. No entanto, é um erro de tempo de compilação para a função ser declarada lexicalmente antes da declaração de uma variável usada na função local (§7.7).
É um erro de tempo de compilação para uma função local declarar um parâmetro, parâmetro de tipo ou variável local com o mesmo nome declarado em qualquer espaço de declaração de variável local delimitador.
Os órgãos de função locais estão sempre acessíveis. O ponto de extremidade de uma declaração de função local poderá ser alcançado se o ponto inicial da declaração de função local estiver acessível.
Exemplo: no exemplo a seguir, o corpo de
Lé alcançável mesmo que o ponto inicial deLnão seja alcançável. Como o ponto inicial deLnão é alcançável, a instrução após o ponto de extremidade deLnão é alcançável: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; } }Em outras palavras, o local de uma declaração de função local não afeta a acessibilidade de nenhuma instrução na função que a contém. exemplo de fim
Se o tipo do argumento para uma função local for dynamic, a função a ser chamada deverá ser resolvida em tempo de compilação, não em tempo de execução.
Uma função local não deve ser usada em uma árvore de expressão.
Uma função local estática
- Pode fazer referência a membros estáticos, parâmetros de tipo, definições constantes e funções locais estáticas do escopo delimitador.
- Não deve fazer referência
thisoubasenem membros de instância de uma referência implícitathis, nem variáveis locais, parâmetros ou funções locais não estáticas do escopo delimitador. No entanto, tudo isso é permitido em umanameof()expressão.
13.7 Declarações de expressão
Um expression_statement avalia uma determinada expressão. O valor calculado pela expressão, se houver, é descartado.
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
;
Nem todas as expressões são permitidas como instruções.
Observação: em particular, expressões como
x + yex == 1, que apenas calculam um valor (que será descartado), não são permitidas como instruções. nota final
A execução de um expression_statement avalia a expressão contida e, em seguida, transfere o controle para o ponto final do expression_statement. O ponto final de um expression_statement é alcançável se esse expression_statement for alcançável.
13.8 Declarações de seleção
13.8.1 Geral
As instruções de seleção selecionam uma das várias instruções possíveis para execução com base no valor de alguma expressão.
selection_statement
: if_statement
| switch_statement
;
13.8.2 A instrução if
A if instrução seleciona uma instrução para execução com base no valor de uma expressão booleana.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Uma else parte é associada ao léxico anterior if mais próximo que é permitido pela sintaxe.
Exemplo: Assim, uma
ifdeclaração do formulárioif (x) if (y) F(); else G();é equivalente a
if (x) { if (y) { F(); } else { G(); } }exemplo de fim
Uma if instrução é executada da seguinte maneira:
- O boolean_expression (§12.26) é avaliado.
- Se a expressão booleana produzir
true, o controle será transferido para a primeira instrução incorporada. Quando e se o controle atingir o ponto final dessa instrução, oifcontrole será transferido para o ponto final da instrução. - Se a expressão booleana for produzida
falsee se umaelseparte estiver presente, o controle será transferido para a segunda instrução incorporada. Quando e se o controle atingir o ponto final dessa instrução, oifcontrole será transferido para o ponto final da instrução. - Se a expressão booleana for produzida
falsee se umaelseparte não estiver presente, oifcontrole será transferido para o ponto final da instrução.
A primeira instrução incorporada de uma if instrução pode ser acessada se a if instrução for alcançável e a expressão booleana não tiver o valor falseconstante .
A segunda instrução incorporada de uma if instrução, se presente, pode ser acessada se a if instrução for alcançável e a expressão booleana não tiver o valor trueconstante .
O ponto final de uma if instrução é alcançável se o ponto final de pelo menos uma de suas instruções inseridas for alcançável. Além disso, o ponto final de uma if instrução sem parte else é alcançável se a if instrução for alcançável e a expressão booleana não tiver o valor trueconstante .
13.8.3 A instrução switch
A switch instrução seleciona para execução uma lista de instruções com um rótulo de comutador associado que corresponde ao valor do selector_expression do comutador.
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
;
Um switch_statement consiste na palavra-chave switch, seguida por uma expressão tuple_expression ou parêntese (cada uma delas chamada selector_expression), seguida por um switch_block. O switch_block consiste em zero ou mais switch_sections, entre chaves. Cada switch_section consiste em um ou mais switch_labels seguidos por um statement_list (§13.3.2). Cada switch_label que case contém tem um padrão associado (§11) no qual o valor do selector_expression do comutador é testado. Se case_guard estiver presente, sua expressão deve ser implicitamente conversível para o tipo bool e essa expressão é avaliada como uma condição adicional para que o caso seja considerado satisfeito.
Observação: por conveniência, os parênteses em switch_statement podem ser omitidos quando o selector_expression é um tuple_expression. Por exemplo,
switch ((a, b)) …pode ser escrito comoswitch (a, b) …. nota final
O tipo de controle de uma switch instrução é estabelecido pelo selector_expression do comutador.
- Se o tipo do selector_expression do comutador for
sbyte, ,byte,short,ushort,int,uint,long,ulong, ,char,bool,stringou um enum_type, ou se for o tipo de valor anulável correspondente a um desses tipos, esse será o tipo de controle daswitchinstrução. - Caso contrário, se existir exatamente uma conversão implícita definida pelo usuário do tipo do selector_expression do comutador para um dos seguintes tipos de controle possíveis:
sbyte, ,byte,short,ushort,longulonguintint, ,char,stringou, um tipo de valor anulável correspondente a um desses tipos, o tipo convertido é o tipo de controle daswitchinstrução. - Caso contrário, o tipo de controle da
switchinstrução é o tipo de selector_expression do comutador. É um erro se esse tipo não existir.
Pode haver no máximo um default rótulo em uma switch declaração.
É um erro se o padrão de qualquer rótulo de opção não for aplicável (§11.2.1) ao tipo da expressão de entrada.
É um erro se o padrão de qualquer rótulo de switch for subsumido por (§11.3) o conjunto de padrões de rótulos de switch anteriores da instrução switch que não têm um protetor de caso ou cujo protetor de caso é uma expressão constante com o valor true.
Exemplo:
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. }exemplo de fim
Uma switch instrução é executada da seguinte maneira:
- O selector_expression do comutador é avaliado e convertido no tipo de controle.
- O controle é transferido de acordo com o valor do selector_expression do comutador convertido:
- O primeiro padrão lexicamente no conjunto de
caserótulos na mesmaswitchinstrução que corresponde ao valor do selector_expression do comutador e, para o qual a expressão de proteção está ausente ou avaliada como true, faz com que o controle seja transferido para a lista de instruções após o rótulo correspondentecase. - Caso contrário, se um
defaultrótulo estiver presente, o controle será transferido para a lista de instruções após odefaultrótulo. - Caso contrário, o
switchcontrole será transferido para o ponto final da instrução.
- O primeiro padrão lexicamente no conjunto de
Nota: A ordem na qual os padrões são correspondidos no tempo de execução não está definida. Um compilador tem permissão (mas não é necessário) para corresponder padrões fora de ordem e reutilizar os resultados de padrões já correspondentes para calcular o resultado da correspondência de outros padrões. No entanto, é necessário um compilador para determinar o primeiro padrão, em ordem lexical, que corresponde à expressão e para o qual a cláusula de guarda está ausente ou é avaliada como
true. nota final
Se o ponto final da lista de instruções de uma seção switch estiver acessível, ocorrerá um erro em tempo de compilação. Isso é conhecido como a regra "sem queda".
Exemplo: O exemplo
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }é válido porque nenhuma seção de switch tem um ponto final alcançável. Ao contrário de C e C++, a execução de uma seção switch não tem permissão para "cair" para a próxima seção switch, e o exemplo
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }resulta em um erro em tempo de compilação. Quando a execução de uma seção switch deve ser seguida pela execução de outra seção switch, uma instrução ou
goto caseexplícitagoto defaultdeve ser usada:switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }exemplo de fim
Vários rótulos são permitidos em um switch_section.
Exemplo: O exemplo
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }é válido. O exemplo não viola a regra "sem queda" porque os rótulos
case 2:edefault:fazem parte do mesmo switch_section.exemplo de fim
Observação: a regra "no fall through" evita uma classe comum de bugs que ocorrem em C e C++ quando
breakas instruções são omitidas acidentalmente. Por exemplo, asswitchseções da instrução acima podem ser revertidas sem afetar o comportamento da instrução:switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }nota final
Observação: a lista de instruções de uma seção switch normalmente termina em uma
breakinstrução ,goto case, orgoto default, mas qualquer construção que torne o ponto final da lista de instruções inacessível é permitida. Por exemplo, sabe-se que umawhileinstrução controlada pela expressãotruebooleana nunca atinge seu ponto final. Da mesma forma, umathrowinstrução orreturnsempre transfere o controle para outro lugar e nunca atinge seu ponto final. Assim, o exemplo a seguir é válido:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }nota final
Exemplo: O tipo regulador de uma
switchinstrução pode ser o tipostring. Por exemplo:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }exemplo de fim
Observação: Assim como os operadores de igualdade de cadeia de caracteres (§12.14.8), a
switchinstrução diferencia maiúsculas de minúsculas e executará uma determinada seção de comutador somente se a cadeia de caracteres selector_expression do comutador corresponder exatamente a umacaseconstante de rótulo. nota final
Quando o tipo de controle de uma switch instrução é string ou um tipo de valor anulável, o valor null é permitido como uma case constante de rótulo.
Os statement_listde um switch_block podem conter declarações de declaração (§13.6). O escopo de uma variável local ou constante declarada em um bloco de comutação é o bloco de comutação.
Um rótulo de switch poderá ser acessado se pelo menos uma das seguintes opções for verdadeira:
- O selector_expression do comutador é um valor constante e
- o rótulo é um
casecujo padrão corresponderia (§11.2.1) a esse valor, e a proteção do rótulo está ausente ou não é uma expressão constante com o valor falso; ou - É um
defaultrótulo e nenhuma seção de opção contém um rótulo de caso cujo padrão corresponderia a esse valor e cuja proteção está ausente ou uma expressão constante com o valor true.
- o rótulo é um
- O selector_expression do comutador não é um valor constante e também
- O rótulo é um
casesem proteção ou com uma proteção cujo valor não é a constante false; ou - é um
defaultrótulo e- o conjunto de padrões que aparecem entre os casos da instrução switch que não têm proteções ou têm proteções cujo valor é a constante verdadeira, não é exaustivo (§11.4) para o tipo de controle switch; ou
- O tipo de controle de switch é um tipo anulável e o conjunto de padrões que aparecem entre os casos da instrução switch que não têm guardas ou têm guardas cujo valor é a constante true não contém um padrão que corresponda ao valor
null.
- O rótulo é um
- O rótulo do switch é referenciado por uma instrução ou
goto casealcançávelgoto default.
A lista de instruções de uma determinada seção de switch pode ser acessada se a switch instrução for acessível e a seção de switch contiver um rótulo de switch alcançável.
O ponto final de uma switch instrução será alcançável se a instrução switch for alcançável e pelo menos uma das seguintes opções for verdadeira:
- A
switchinstrução contém uma instrução alcançávelbreakque sai daswitchinstrução. - Nenhum
defaultrótulo está presente e também- O selector_expression do comutador é um valor não constante e o conjunto de padrões que aparecem entre os casos da instrução switch que não têm guardas ou têm guardas cujo valor é a constante true, não é exaustivo (§11.4) para o tipo de controle de comutador.
- O selector_expression do comutador é um valor não constante de um tipo anulável e nenhum padrão aparecendo entre os casos da instrução switch que não têm guardas ou têm guardas cujo valor é a constante true corresponderia ao valor
null. - O selector_expression do comutador é um valor constante e nenhum
caserótulo sem um guarda ou cuja proteção é a constante true corresponderia a esse valor.
Exemplo: O código a seguir mostra um uso sucinto da
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"; } }O caso var corresponde a
null, à cadeia de caracteres vazia ou a qualquer cadeia de caracteres que contenha apenas espaço em branco. exemplo de fim
13.9 Instruções de iteração
13.9.1 Geral
As instruções de iteração executam repetidamente uma instrução incorporada.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 A instrução while
A while instrução executa condicionalmente uma instrução incorporada zero ou mais vezes.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Uma while instrução é executada da seguinte maneira:
- O boolean_expression (§12.26) é avaliado.
- Se a expressão booleana produzir
true, o controle será transferido para a instrução incorporada. Quando e se o controle atingir o ponto final da instrução incorporada (possivelmente da execução de umacontinueinstrução), owhilecontrole será transferido para o início da instrução. - Se a expressão booleana produzir
false, o controle será transferido para o ponto final dawhileinstrução.
Dentro da instrução incorporada de uma while instrução, uma break instrução (§13.10.2) pode ser usada para transferir o controle para o ponto final da while instrução (encerrando assim a iteração da instrução incorporada) e uma continue instrução (§13.10.3) pode ser usada para transferir o controle para o ponto final da instrução incorporada (executando assim outra iteração da while instrução).
A instrução incorporada de uma while instrução pode ser acessada se a while instrução for alcançável e a expressão booleana não tiver o valor falseconstante .
O ponto final de uma while instrução pode ser alcançado se pelo menos uma das seguintes condições for verdadeira:
- A
whileinstrução contém uma instrução alcançávelbreakque sai dawhileinstrução. - A
whileinstrução é alcançável e a expressão booleana não tem o valortrueconstante .
13.9.3 A instrução do
A do instrução executa condicionalmente uma instrução incorporada uma ou mais vezes.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Uma do instrução é executada da seguinte maneira:
- O controle é transferido para a instrução incorporada.
- Quando e se o controle atingir o ponto final da instrução inserida (possivelmente da execução de uma
continueinstrução), o boolean_expression (§12.26) será avaliado. Se a expressão booleana produzirtrue, o controle será transferido para o início dadoinstrução. Caso contrário, odocontrole será transferido para o ponto final da instrução.
Dentro da instrução incorporada de uma do instrução, uma break instrução (§13.10.2) pode ser usada para transferir o controle para o ponto final da do instrução (encerrando assim a iteração da instrução incorporada) e uma continue instrução (§13.10.3) pode ser usada para transferir o controle para o ponto final da instrução incorporada (executando assim outra iteração da do instrução).
A instrução incorporada de uma do instrução pode ser acessada se a do instrução estiver acessível.
O ponto final de uma do instrução pode ser alcançado se pelo menos uma das seguintes condições for verdadeira:
- A
doinstrução contém uma instrução alcançávelbreakque sai dadoinstrução. - O ponto final da instrução incorporada é alcançável e a expressão booleana não tem o valor
trueconstante .
13.9.4 A declaração for
A for instrução avalia uma sequência de expressões de inicialização e, em seguida, enquanto uma condição é verdadeira, executa repetidamente uma instrução incorporada e avalia uma sequência de expressões de iteração.
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)*
;
A for_initializer, se presente, consiste em uma local_variable_declaration (§13.6.2) ou uma lista de statement_expressions (§13.7) separados por vírgulas. O escopo de uma variável local declarada por um for_initializer é o for_initializer, for_condition, for_iterator e embedded_statement.
O for_condition, se presente, será um boolean_expression (§12.26).
A for_iterator, se presente, consiste em uma lista de statement_expressions (§13.7) separados por vírgulas.
Uma for instrução é executada da seguinte maneira:
- Se um for_initializer estiver presente, os inicializadores de variáveis ou expressões de instrução serão executados na ordem em que foram gravados. Esta etapa é executada apenas uma vez.
- Se um for_condition estiver presente, ele será avaliado.
- Se o for_condition não estiver presente ou se a avaliação resultar
true, o controle será transferido para a instrução incorporada. Quando e se o controle atingir o ponto final da instrução incorporada (possivelmente da execução de umacontinueinstrução), as expressões do for_iterator, se houver, serão avaliadas em sequência e, em seguida, outra iteração será executada, começando com a avaliação do for_condition na etapa acima. - Se o for_condition estiver presente e a avaliação resultar
false, o controle será transferido para o ponto final daforinstrução.
Dentro da instrução incorporada de uma for instrução, uma break instrução (§13.10.2) pode ser usada para transferir o controle para o ponto final da for instrução (encerrando assim a iteração da instrução incorporada) e uma continue instrução (§13.10.3) pode ser usada para transferir o controle para o ponto final da instrução incorporada (executando assim o for_iterator e executando outra iteração da for instrução, começando com o for_condition).
A instrução incorporada de uma for instrução pode ser acessada se uma das seguintes condições for verdadeira:
- A
fordeclaração está acessível e nenhuma for_condition está presente. - A
forinstrução é alcançável e um for_condition está presente e não tem o valorfalseconstante .
O ponto final de uma for instrução pode ser alcançado se pelo menos uma das seguintes condições for verdadeira:
- A
forinstrução contém uma instrução alcançávelbreakque sai daforinstrução. - A
forinstrução é alcançável e um for_condition está presente e não tem o valortrueconstante .
13.9.5 A instrução foreach
13.9.5.1 Geral
A foreach instrução enumera os elementos de uma coleção, executando uma instrução incorporada para cada elemento da coleção.
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
;
O local_variable_type e o identificador de uma instrução foreach declaram a variável de iteração da instrução. Se o var identificador for fornecido como o local_variable_type e nenhum tipo nomeado var estiver no escopo, a variável de iteração será considerada uma variável de iteração digitada implicitamente e seu tipo será considerado o tipo de elemento da foreach instrução, conforme especificado abaixo.
É um erro de tempo de compilação se ambos await e ref_kind estiverem presentes em um foreach statement.
Se o foreach_statement contiver ambos ou nenhum ref e readonly, a variável de iteração denota uma variável que é tratada como somente leitura. Caso contrário, se foreach_statement contiver ref sem readonly, a variável de iteração denota uma variável que deve ser gravável.
A variável de iteração corresponde a uma variável local com um escopo que se estende sobre a instrução incorporada. Durante a execução de uma foreach instrução, a variável de iteração representa o elemento de coleção para o qual uma iteração está sendo executada no momento. Se a variável de iteração denotar uma variável somente leitura, ocorrerá um erro em tempo de compilação se a instrução incorporada tentar modificá-la (por meio de atribuição ou dos ++ operadores and -- ) ou passá-la como um parâmetro de referência ou saída.
O processamento em tempo de compilação de uma foreach instrução primeiro determina o tipo de coleção (C), o tipo de enumerador (E) e o tipo de iteração (Tref Tou ref readonly T) da expressão.
A determinação é semelhante para as versões síncronas e assíncronas. Interfaces diferentes com diferentes métodos e tipos de retorno distinguem as versões síncronas e assíncronas. O processo geral prossegue da seguinte maneira. Os nomes em '«' e '»' são espaços reservados para os nomes reais para iteradores síncronos e assíncronos. Os tipos permitidos para «GetEnumerator», «MoveNext», «IEnumerable»T, «IEnumerator»<T> e quaisquer outras distinções são detalhados em < para uma instrução síncrona > e em §13.9.5.3 para uma instrução assíncronaforeach.foreach
- Determine se o tipo
Xde expressão tem um método "GetEnumerator" apropriado:- Execute a pesquisa de membro no tipo
Xcom o identificador «GetEnumerator» e nenhum argumento de tipo. Se a pesquisa de membro não produzir uma correspondência ou produzir uma ambiguidade ou produzir uma correspondência que não seja um grupo de métodos, verifique se há uma interface enumerável conforme descrito na etapa 2. É recomendável que um aviso seja emitido se a pesquisa de membro produzir qualquer coisa, exceto um grupo de métodos ou nenhuma correspondência. - Execute a resolução de sobrecarga usando o grupo de métodos resultante e uma lista de argumentos vazia. Se a resolução de sobrecarga não resultar em métodos aplicáveis, resultar em uma ambiguidade ou resultar em um único melhor método, mas esse método for estático ou não público, verifique se há uma interface enumerável, conforme descrito abaixo. É recomendável que um aviso seja emitido se a resolução de sobrecarga produzir qualquer coisa, exceto um método de instância pública inequívoco ou nenhum método aplicável.
- Se o tipo
Ede retorno do método "GetEnumerator" não for uma classe, struct ou tipo de interface, produza um erro e não execute nenhuma etapa adicional. - Execute a pesquisa de
Emembro com o identificadorCurrente nenhum argumento de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, o resultado será um erro ou o resultado será qualquer coisa, exceto uma propriedade de instância pública que permita a leitura, produza um erro e não execute mais nenhuma etapa. - Execute a pesquisa de
Emembro com o identificador «MoveNext» e nenhum argumento de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, o resultado será um erro ou o resultado será qualquer coisa, exceto um grupo de métodos, produzirá um erro e não executará mais nenhuma etapa. - Execute a resolução de sobrecarga no grupo de métodos com uma lista de argumentos vazia. Se a resolução de sobrecarga resultar em: nenhum método aplicável; uma ambiguidade; ou um único método melhor, mas esse método é estático ou não é público ou seu tipo de retorno não é um tipo de retorno permitido; em seguida, produza um erro e não execute mais nenhuma etapa.
- O tipo de coleção é
X, o tipo de enumerador éE, e o tipo de iteração é o tipo daCurrentpropriedade.
- Execute a pesquisa de membro no tipo
- Caso contrário, verifique se há uma interface enumerável:
- Se entre todos os tipos
Tᵢpara os quais há uma conversão implícita deX"IEnumerable»<Ti>, há um tipoTexclusivo queTnãodynamicé e, para todos os outrosTᵢ, há uma conversão implícita de «IEnumerable»<T> para «IEnumerable»<Ti>, então o tipo de coleção é a interface «IEnumerable»<T>, o tipo enumerador é a interface «IEnumerator»<T>, e o tipo de iteração éT. - Caso contrário, se houver mais de um tipo desse tipo
T, produza um erro e não execute mais nenhuma etapa.
- Se entre todos os tipos
Observação: se a expressão tiver o valor
null, umSystem.NullReferenceExceptionserá gerado em tempo de execução. nota final
Uma implementação tem permissão para implementar um determinado foreach_statement de forma diferente; Por exemplo, por motivos de desempenho, desde que o comportamento seja consistente com as expansões descritas em §13.9.5.2 e §13.9.5.3.
13.9.5.2 Foreach síncrono
Uma palavra-chave síncrona foreach não inclui a await palavra-chave antes da foreach palavra-chave. A determinação de tipo de coleção, tipo de enumeração e tipo de iteração prossegue conforme descrito em §13.9.5.1, em que:
- "GetEnumerator" é um
GetEnumeratormétodo. - «MoveNext» é um
MoveNextmétodo com umbooltipo de retorno. - «IEnumerable»<T> é a
System.Collections.Generic.IEnumerable<T>interface. - «IEnumerator»<T> é a
System.Collections.Generic.IEnumerator<T>interface.
Além disso, as seguintes modificações são feitas nas etapas em §13.9.5.1:
Antes do processo descrito em §13.9.5.1, as seguintes etapas são tomadas:
- Se o tipo
Xde expressão for um tipo de matriz, haverá uma conversão de referência implícita daXIEnumerable<T>interface em queTestá o tipo de elemento da matrizX(§17.2.3). - Se o tipo
Xde expressão fordynamic, haverá uma conversão implícita da expressão para aIEnumerableinterface (§10.2.10). O tipo de coleção é aIEnumerableinterface e o tipo de enumerador é aIEnumeratorinterface. Se ovaridentificador for dado como o local_variable_type então o tipo de iteração édynamic, caso contrário, éobject.
Se o processo em §13.9.5.1 for concluído sem produzir um único tipo de coleção, tipo de enumerador e tipo de iteração, as seguintes etapas serão executadas:
- Se houver uma conversão implícita de para a
Xinterface, o tipo deSystem.Collections.IEnumerablecoleção será essa interface, o tipo de enumerador será a interfaceSystem.Collections.IEnumeratore o tipo de iteração seráobject. - Caso contrário, um erro será produzido e nenhuma etapa adicional será executada.
Uma foreach declaração do formulário
foreach (V v in x) «embedded_statement»
é então equivalente a:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
A variável e não é visível ou acessível para a expressão x ou a instrução incorporada ou qualquer outro código-fonte do programa. A variável v é somente leitura na instrução incorporada. Se não houver uma conversão explícita (§10.3) de (o tipo de T iteração) para V (o local_variable_type na foreach instrução), um erro será produzido e nenhuma outra etapa será executada.
Quando a variável de iteração é uma variável de referência (§9.7), uma foreach instrução do formulário
foreach (ref V v in x) «embedded_statement»
é então equivalente a:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
A variável e não é visível ou acessível para a expressão x ou a instrução incorporada ou qualquer outro código-fonte do programa. A variável v de referência é leitura-gravação na instrução inserida, mas v não deve ser reatribuída (§12.23.3). Se não houver uma conversão de identidade (§10.2.2) de (o tipo de T iteração) para V (o local_variable_type na foreach instrução), um erro será produzido e nenhuma outra etapa será executada.
Uma foreach instrução do formulário foreach (ref readonly V v in x) «embedded_statement» tem um formulário equivalente semelhante, mas a variável v de referência está ref readonly na instrução incorporada e, portanto, não pode ser reatribuída ou reatribuída por referência.
O posicionamento de dentro do vwhile loop é importante para como ele é capturado (§12.21.6.2) por qualquer função anônima que ocorra no embedded_statement.
Exemplo:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();Se
vna forma expandida fosse declarado fora dowhileloop, ele seria compartilhado entre todas as iterações, e seu valor após oforloop seria o valor final,13, que é o que a invocação defimprimiria. Em vez disso, como cada iteração tem sua própria variávelv, aquela capturada porfna primeira iteração continuará a conter o valor7, que é o que será impresso. (Observe que as versões anteriores do C# eram declaradasvfora dowhileloop.)exemplo de fim
O corpo do finally bloco é construído de acordo com as seguintes etapas:
Se houver uma conversão implícita de
Epara aSystem.IDisposableinterface, entãoSe
Efor um tipo de valor não anulável, afinallycláusula será expandida para o equivalente semântico de:finally { ((System.IDisposable)e).Dispose(); }Caso contrário, a
finallycláusula é expandida para o equivalente semântico de:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }exceto que, se
Efor um tipo de valor ou um parâmetro de tipo instanciado em um tipo de valor, a conversão de TOenão fará com que a conversão deSystem.IDisposableboxing ocorra.
Caso contrário, if
Eé um tipo selado, afinallycláusula é expandida para um bloco vazio:finally {}Caso contrário, a
finallycláusula é expandida para:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
A variável d local não é visível ou acessível a nenhum código de usuário. Em particular, não entra em conflito com nenhuma outra variável cujo escopo inclua o finally bloco.
A ordem na qual foreach percorre os elementos de uma matriz é a seguinte: Para matrizes unidimensionais, os elementos são percorridos em ordem crescente de índice, começando com o índice 0 e terminando com o índice Length – 1. Para matrizes multidimensionais, os elementos são percorridos de forma que os índices da dimensão mais à direita sejam aumentados primeiro, depois a próxima dimensão à esquerda e assim por diante para a esquerda.
Exemplo: O exemplo a seguir imprime cada valor em uma matriz bidimensional, na ordem dos elementos:
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(); } }A saída produzida é a seguinte:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9exemplo de fim
Exemplo: No exemplo a seguir
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }o tipo de é inferido
ncomo ,into tipo de iteração denumbers.exemplo de fim
13.9.5.3 await foreach
Um foreach assíncrono usa a await foreach sintaxe. A determinação de tipo de coleção, tipo de enumeração e tipo de iteração prossegue conforme descrito em §13.9.5.1, em que:
- «GetEnumerator» é um
GetEnumeratorAsyncmétodo que tem um tipo de retorno aguardado (§12.9.9.2). - «MoveNext» é um
MoveNextAsyncmétodo que tem um tipo de retorno aguardado (§12.9.9.2) em que o await_expression é classificado como umbool(§12.9.9.3). - «IEnumerable»<T> é a
System.Collections.Generic.IAsyncEnumerable<T>interface. - «IEnumerator»<T> é a
System.Collections.Generic.IAsyncEnumerator<T>interface.
É um erro para o tipo de iteração de uma instrução await foreach ser uma variável de referência (§9,7).
Uma instrução await foreach do formulário
await foreach (T item in enumerable) «embedded_statement»
é semanticamente 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.
}
No caso em que a expressão enumerable representa uma expressão de chamada de método e um dos parâmetros é marcado com o EnumeratorCancellationAttribute (§23.5.8) que CancellationToken é passado para o GetAsyncEnumerator método. Outros métodos de biblioteca podem exigir que um CancellationToken seja passado para GetAsyncEnumerator. Quando esses métodos fazem parte da expressão enumerable, os tokens devem ser combinados em um único token como se fossem por CreateLinkedTokenSource e sua Token propriedade.
O corpo do finally bloco é construído de acordo com as seguintes etapas:
Se
Etiver um método acessívelDisposeAsync()em que o tipo de retorno é aguardado (§12.9.9.2), afinallycláusula será expandida para o equivalente semântico de:finally { await e.DisposeAsync(); }Caso contrário, se houver uma conversão implícita de
Epara aSystem.IAsyncDisposableinterface eEfor um tipo de valor não anulável, afinallycláusula será expandida para o equivalente semântico de:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }exceto que, se
Efor um tipo de valor ou um parâmetro de tipo instanciado em um tipo de valor, a conversão de TOenão fará com que a conversão deSystem.IAsyncDisposableboxing ocorra.Caso contrário, se
Efor umref structtipo e tiver um método acessívelDispose(), afinallycláusula será expandida para o equivalente semântico de:finally { e.Dispose(); }Caso contrário, if
Eé um tipo selado, afinallycláusula é expandida para um bloco vazio:finally {}Caso contrário, a
finallycláusula é expandida para:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
A variável d local não é visível ou acessível a nenhum código de usuário. Em particular, não entra em conflito com nenhuma outra variável cujo escopo inclua o finally bloco.
Observação
await foreach: não é necessário descartar deeforma síncrona se um mecanismo de descarte assíncrono não estiver disponível. nota final
13.10 Instruções de salto
13.10.1 Geral
As instruções Jump transferem o controle incondicionalmente.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
O local para o qual uma instrução jump transfere o controle é chamado de destino da instrução jump.
Quando uma instrução jump ocorre dentro de um bloco e o destino dessa instrução jump está fora desse bloco, diz-se que a instrução jump sai do bloco. Embora uma instrução jump possa transferir o controle para fora de um bloco, ela nunca pode transferir o controle para um bloco.
A execução de instruções de salto é complicada pela presença de instruções intermediárias try . Na ausência de tais try instruções, uma instrução jump transfere incondicionalmente o controle da instrução jump para seu destino. Na presença de tais instruções intermediárias try , a execução é mais complexa. Se a instrução jump sair de um ou mais try blocos com blocos associados finally , o controle será inicialmente transferido para o finally bloco da instrução mais try interna. Quando e se o controle atingir o ponto final de um finally bloco, o controle será transferido para o finally bloco da próxima instrução delimitadora try . Esse processo é repetido até que os finally blocos de todas as instruções intermediárias try tenham sido executados.
Exemplo: no código a seguir
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"); } }Os
finallyblocos associados a duastryinstruções são executados antes que o controle seja transferido para o destino da instrução Jump. A saída produzida é a seguinte:Before break Innermost finally block Outermost finally block After breakexemplo de fim
13.10.2 A instrução break
A break instrução sai da instrução , switch, whiledo, , ou for delimitadora foreachmais próxima.
break_statement
: 'break' ';'
;
O destino de uma break instrução é o ponto final da instrução , switch, while, doou for delimitadora foreachmais próxima. Se uma break instrução não for delimitada por uma switchinstrução , while, do, for, ou , ocorrerá foreach um erro em tempo de compilação.
Quando várias switchinstruções , while, do, for, ou foreach são aninhadas umas nas outras, uma break instrução se aplica somente à instrução mais interna. Para transferir o controle entre vários níveis de aninhamento, uma goto instrução (§13.10.4) deve ser usada.
Uma break instrução não pode sair de um finally bloco (§13.11). Quando uma break instrução ocorre dentro de um finally bloco, o break destino da instrução deve estar dentro do mesmo finally bloco; caso contrário, ocorre um erro em tempo de compilação.
Uma break instrução é executada da seguinte maneira:
- Se a
breakinstrução sair de um ou maistryblocos com blocos associadosfinally, o controle será inicialmente transferido para ofinallybloco da instrução maistryinterna. Quando e se o controle atingir o ponto final de umfinallybloco, o controle será transferido para ofinallybloco da próxima instrução delimitadoratry. Esse processo é repetido até que osfinallyblocos de todas as instruções intermediáriastrytenham sido executados. - O controle é transferido para o destino da
breakinstrução.
Como uma break instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma break instrução nunca é alcançável.
13.10.3 A instrução continue
A continue instrução inicia uma nova iteração da instrução , while, do, ou for mais próximaforeach.
continue_statement
: 'continue' ';'
;
O destino de uma continue instrução é o ponto final da instrução incorporada da instrução , whiledo, for, ou foreach mais próxima. Se uma continue instrução não for colocada entre uma whileinstrução , do, for, ou, ocorrerá foreach um erro em tempo de compilação.
Quando várias whileinstruções , do, for, ou foreach são aninhadas umas nas outras, uma continue instrução se aplica somente à instrução mais interna. Para transferir o controle entre vários níveis de aninhamento, uma goto instrução (§13.10.4) deve ser usada.
Uma continue instrução não pode sair de um finally bloco (§13.11). Quando uma continue instrução ocorre dentro de um finally bloco, o continue destino da instrução deve estar dentro do mesmo finally bloco; caso contrário, ocorre um erro em tempo de compilação.
Uma continue instrução é executada da seguinte maneira:
- Se a
continueinstrução sair de um ou maistryblocos com blocos associadosfinally, o controle será inicialmente transferido para ofinallybloco da instrução maistryinterna. Quando e se o controle atingir o ponto final de umfinallybloco, o controle será transferido para ofinallybloco da próxima instrução delimitadoratry. Esse processo é repetido até que osfinallyblocos de todas as instruções intermediáriastrytenham sido executados. - O controle é transferido para o destino da
continueinstrução.
Como uma continue instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma continue instrução nunca é alcançável.
13.10.4 A instrução goto
A goto instrução transfere o controle para uma instrução marcada por um rótulo.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
O destino de uma gotoinstrução de identificador é a instrução rotulada com o rótulo fornecido. Se um rótulo com o nome fornecido não existir no membro da função atual ou se a goto instrução não estiver dentro do escopo do rótulo, ocorrerá um erro em tempo de compilação.
Observação: essa regra permite o uso de uma
gotoinstrução para transferir o controle de um escopo aninhado , mas não para um escopo aninhado. No exemploclass 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}]"); } } }Uma
gotoinstrução é usada para transferir o controle para fora de um escopo aninhado.nota final
O destino de uma goto case instrução é a lista de instruções na instrução imediatamente delimitadora switch (§13.8.3) que contém um case rótulo com um padrão constante do valor constante fornecido e nenhuma proteção. Se a goto case instrução não for delimitada por uma switch instrução, se a instrução delimitadora switch mais próxima não contiver tal case, ou se a constant_expression não for implicitamente conversível (§10.2) para o tipo determinante da instrução delimitadora switch mais próxima, ocorrerá um erro em tempo de compilação.
O destino de uma goto default instrução é a lista de instruções na instrução imediatamente delimitadora switch (§13.8.3), que contém um default rótulo. Se a goto default instrução não for delimitada por uma switch instrução ou se a instrução delimitadora switch mais próxima não contiver um default rótulo, ocorrerá um erro em tempo de compilação.
Uma goto instrução não pode sair de um finally bloco (§13.11). Quando uma goto instrução ocorre dentro de um finally bloco, o goto destino da instrução deve estar dentro do mesmo finally bloco ou, caso contrário, ocorre um erro em tempo de compilação.
Uma goto instrução é executada da seguinte maneira:
- Se a
gotoinstrução sair de um ou maistryblocos com blocos associadosfinally, o controle será inicialmente transferido para ofinallybloco da instrução maistryinterna. Quando e se o controle atingir o ponto final de umfinallybloco, o controle será transferido para ofinallybloco da próxima instrução delimitadoratry. Esse processo é repetido até que osfinallyblocos de todas as instruções intermediáriastrytenham sido executados. - O controle é transferido para o destino da
gotoinstrução.
Como uma goto instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma goto instrução nunca é alcançável.
13.10.5 A declaração de retorno
A return instrução retorna o controle para o chamador atual do membro da função no qual a instrução return aparece, retornando opcionalmente um valor ou um variable_reference (§9.5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Um return_statement sem expressão é chamado de retorno sem valor; um contendo expressão é chamado deref; e um contendo apenas expressão é chamado de retorno por valor.
É um erro de tempo de compilação usar um return-no-value de um método declarado como sendo returns-by-value ou returns-by-ref (§15.6.1).
É um erro de tempo de compilação usar um retorno por ref de um método declarado como sendo retornado sem valor ou retornado por valor.
É um erro em tempo de compilação usar um retorno por valor de um método declarado como sendo retornado sem valor ou retornado por referência.
É um erro de tempo de compilação usar um return-by-ref se expression não for um variable_reference ou for uma referência a uma variável cujo ref-safe-context não é caller-context (§9.7.2).
É um erro de tempo de compilação usar um retorno por ref de um método declarado com o method_modifierasync.
Diz-se que um membro de função calcula um valor se for um método com um método de retornos por valor (§15.6.11), um acessador get de retornos por valor de uma propriedade ou indexador ou um operador definido pelo usuário. Os membros de função que são returns-no-value não calculam um valor e são métodos com o tipo voidde retorno efetivo, definir acessadores de propriedades e indexadores, adicionar e remover acessadores de eventos, construtores de instância, construtores estáticos e finalizadores. Os membros da função que são retornados por ref não calculam um valor.
Para um retorno por valor, deve existir uma conversão implícita (§10.2) do tipo de expressão para o tipo de retorno efetivo (§15.6.11) do membro da função que o contém. Para um retorno por ref, deve existir uma conversão de identidade (§10.2.2) entre o tipo de expressão e o tipo de retorno efetivo do membro da função que o contém.
return as instruções também podem ser usadas no corpo de expressões de função anônima (§12.21) e participar na determinação de quais conversões existem para essas funções (§10.7.1).
É um erro de tempo de compilação para uma return instrução aparecer em um finally bloco (§13.11).
Uma return instrução é executada da seguinte maneira:
- Para um retorno por valor, a expressão é avaliada e seu valor é convertido no tipo de retorno efetivo da função que o contém por uma conversão implícita. O resultado da conversão torna-se o valor do resultado produzido pela função. Para um retorno por ref, a expressão é avaliada e o resultado deve ser classificado como uma variável. Se o retorno por ref do método delimitador incluir
readonly, a variável resultante será somente leitura. - Se a
returninstrução for delimitada por um ou maistryblocos oucatchcom blocos associadosfinally, o controle será inicialmente transferido para ofinallybloco da instrução maistryinterna. Quando e se o controle atingir o ponto final de umfinallybloco, o controle será transferido para ofinallybloco da próxima instrução delimitadoratry. Esse processo é repetido até que osfinallyblocos de todas as instruções delimitadorastrytenham sido executados. - Se a função que contém não for uma função assíncrona, o controle será retornado ao chamador da função que contém junto com o valor do resultado, se houver.
- Se a função de contenção for uma função assíncrona, o controle será retornado ao chamador atual e o valor do resultado, se houver, será registrado na tarefa de retorno, conforme descrito em (§15.14.3).
Como uma return instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma return instrução nunca é alcançável.
13.10.6 A instrução throw
A throw instrução gera uma exceção.
throw_statement
: 'throw' expression? ';'
;
Uma throw instrução com uma expressão gera uma exceção produzida pela avaliação da expressão. A expressão deve ser implicitamente conversível em System.Exception, e o resultado da avaliação da expressão é convertido em System.Exception antes de ser lançada. Se o resultado da conversão for null, a System.NullReferenceException é lançado em seu lugar.
Uma throw instrução sem expressão pode ser usada apenas em um catch bloco, nesse caso, essa instrução lança novamente a exceção que está sendo tratada no momento por esse catch bloco.
Como uma throw instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma throw instrução nunca é alcançável.
Quando uma exceção é lançada, o controle é transferido para a primeira catch cláusula em uma instrução delimitadora try que pode lidar com a exceção. O processo que ocorre desde o ponto em que a exceção é lançada até o ponto de transferência do controle para um manipulador de exceção adequado é conhecido como propagação de exceção. A propagação de uma exceção consiste em avaliar repetidamente as etapas a seguir até que uma catch cláusula que corresponda à exceção seja encontrada. Nesta descrição, o ponto de lançamento é inicialmente o local no qual a exceção é lançada. Esse comportamento é especificado em (§22.4).
No membro da função atual, cada
tryinstrução que inclui o ponto de lançamento é examinada. Para cada instruçãoS, começando com a instrução maistryinterna e terminando com a declaração maistryexterna, as seguintes etapas são avaliadas:- Se o
trybloco de incluir o ponto deSlançamento e tiverSuma ou maiscatchcláusulas, ascatchcláusulas serão examinadas em ordem de aparição para localizar um manipulador adequado para a exceção. A primeiracatchcláusula que especifica um tipoTde exceção (ou um parâmetro de tipo que, em tempo de execução, denota um tipoTde exceção), de modo que o tipo de tempo de execução deEderiva deTseja considerado uma correspondência. Se a cláusula contiver um filtro de exceção, o objeto de exceção será atribuído à variável de exceção e o filtro de exceção será avaliado. Quando umacatchcláusula contém um filtro de exceção, essacatchcláusula é considerada uma correspondência se o filtro de exceção for avaliado comotrue. Uma cláusula geralcatch(§13.11) é considerada uma correspondência para qualquer tipo de exceção. Se uma cláusula correspondentecatchfor localizada, a propagação da exceção será concluída transferindo o controle para o bloco dessacatchcláusula. - Caso contrário, se o
trybloco ou umcatchbloco de incluir o ponto deSlançamento e seStiver umfinallybloco, o controle será transferido para ofinallybloco. Se o blocofinallylançar outra exceção, o processamento da exceção atual será encerrado. Caso contrário, quando o controle atingir o ponto final do bloco, ofinallyprocessamento da exceção atual continuará.
- Se o
Se um manipulador de exceção não estiver localizado na invocação da função atual, a invocação da função será encerrada e ocorrerá uma das seguintes situações:
Se a função atual não for assíncrona, as etapas acima serão repetidas para o chamador da função com um ponto de lançamento correspondente à instrução da qual o membro da função foi invocado.
Se a função atual for assíncrona e retornar tarefas, a exceção será registrada na tarefa de retorno, que será colocada em um estado com falha ou cancelada, conforme descrito em §15.14.3.
Se a função atual for assíncrona e
voidcom retorno, o contexto de sincronização da thread atual será notificado conforme descrito em §15.14.4.
Se o processamento de exceção encerrar todas as invocações de membro de função no thread atual, indicando que o thread não tem manipulador para a exceção, o thread será encerrado. O impacto de tal rescisão é definido pela implementação.
13.11 A instrução try
A try instrução fornece um mecanismo para capturar exceções que ocorrem durante a execução de um bloco. Além disso, a try instrução fornece a capacidade de especificar um bloco de código que sempre é executado quando o controle sai da try instrução.
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
;
Um try_statement consiste na palavra-chave try seguida por um bloco, depois zero ou mais catch_clauses e depois um finally_clause opcional. Deve haver pelo menos um catch_clause ou um finally_clause.
Em um exception_specifier o tipo, ou sua classe base efetiva, se for um type_parameter, deve ser System.Exception ou um tipo que deriva dele.
Quando uma catch cláusula especifica um class_type e um identificador, uma variável de exceção do nome e tipo fornecidos é declarada. A variável de exceção é introduzida no espaço de declaração do specific_catch_clause (§7.3). Durante a execução do exception_filterbloco, a variável de exceção representa a exceção que está sendo tratada no momento. Para fins de verificação de atribuição definida, a variável de exceção é considerada atribuída definitivamente em todo o seu escopo.
A menos que uma catch cláusula inclua um nome de variável de exceção, é impossível acessar o objeto de exceção no filtro e catch no bloco.
Uma catch cláusula que não especifica nem um tipo de exceção nem um nome de variável de exceção é chamada de cláusula geral catch . Uma try declaração só pode ter uma cláusula geral catch e, se houver, será a última catch cláusula.
Observação: algumas linguagens de programação podem oferecer suporte a exceções que não são representáveis como um objeto derivado de
System.Exception, embora essas exceções nunca possam ser geradas pelo código C#. Uma cláusula geralcatchpode ser usada para capturar essas exceções. Assim, uma cláusula geralcatché semanticamente diferente de uma que especifica o tipoSystem.Exception, em que a primeira também pode capturar exceções de outras linguagens. nota final
Para localizar um manipulador para uma exceção, catch as cláusulas são examinadas em ordem lexical. Se uma catch cláusula especificar um tipo, mas nenhum filtro de exceção, será um erro de tempo de compilação para uma cláusula posterior catch da mesma try instrução especificar um tipo que seja igual ou derivado desse tipo.
Nota: Sem essa restrição, seria possível escrever cláusulas inacessíveis
catch. nota final
Dentro de um catch bloco, uma throw instrução (§13.10.6) sem expressão pode ser usada para lançar novamente a exceção que foi capturada catch pelo bloco. As atribuições a uma variável de exceção não alteram a exceção que é lançada novamente.
Exemplo: no código a seguir
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); } } }O método
Fcaptura uma exceção, grava algumas informações de diagnóstico no console, altera a variável de exceção e gera novamente a exceção. A exceção que é lançada novamente é a exceção original, portanto, a saída produzida é:Exception in F: G Exception in Main: GSe o primeiro
catchbloco tivesse lançadoeem vez de relançar a exceção atual, a saída produzida seria a seguinte:Exception in F: G Exception in Main: Fexemplo de fim
É um erro de tempo de compilação para uma breakinstrução , continue, ou goto transferir o controle para fora de um finally bloco. Quando uma breakinstrução , continue, or goto ocorre em um finally bloco, o destino da instrução deve estar dentro do mesmo finally bloco ou, caso contrário, ocorre um erro em tempo de compilação.
É um erro de tempo de compilação que uma return instrução ocorra em um finally bloco.
Quando a execução atinge uma try instrução, o controle é transferido para o try bloco. Se o try controle atingir o ponto final do bloco sem que uma exceção seja propagada, o controle será transferido para o finally bloco, se houver. Se não houver nenhum finally bloco, o try controle será transferido para o ponto final da instrução.
Se uma exceção foi propagada, as catch cláusulas, se houver, são examinadas em ordem lexical buscando a primeira correspondência para a exceção. A busca por uma cláusula correspondente catch continua com todos os blocos delimitadores, conforme descrito em §13.10.6. Uma catch cláusula será uma correspondência se o tipo de exceção corresponder a qualquer exception_specifier e qualquer exception_filter for verdadeira. Uma catch cláusula sem um exception_specifier corresponde a qualquer tipo de exceção. O tipo de exceção corresponde ao exception_specifier quando o exception_specifier especifica o tipo de exceção ou um tipo base do tipo de exceção. Se a cláusula contiver um filtro de exceção, o objeto de exceção será atribuído à variável de exceção e o filtro de exceção será avaliado. Se a avaliação do boolean_expression para um exception_filter gerar uma exceção, essa exceção será capturada e o filtro de exceção será avaliado como false.
Se uma exceção tiver sido propagada e uma cláusula correspondente catch for encontrada, o controle será transferido para o primeiro bloco correspondente catch . Se o catch controle atingir o ponto final do bloco sem que uma exceção seja propagada, o controle será transferido para o finally bloco, se houver. Se não houver nenhum finally bloco, o try controle será transferido para o ponto final da instrução. Se uma exceção tiver sido propagada do bloco, o catch controle será transferido para o finally bloco, se houver. A exceção é propagada para a próxima instrução delimitadora try .
Se uma exceção tiver sido propagada e nenhuma cláusula correspondente catch for encontrada, o controle será transferido para o finally bloco, se ele existir. A exceção é propagada para a próxima instrução delimitadora try .
As instruções de um bloco finally são sempre executadas quando o controle deixa uma instrução try. Isso é verdadeiro se a transferência de controle ocorrer como resultado da execução normal, como resultado da execução de uma breakinstrução , continue, goto, ou return ou como resultado da propagação de uma exceção para fora da try instrução. Se o controle atingir o ponto final do finally bloco sem que uma exceção seja propagada, o try controle será transferido para o ponto final da instrução.
Se uma exceção for lançada durante a execução de um finally bloco e não for capturada dentro do mesmo finally bloco, a exceção será propagada para a próxima instrução delimitadora try . Se outra exceção estava em processo de propagação, essa exceção é perdida. O processo de propagação de uma exceção é discutido mais adiante na descrição da throw instrução (§13.10.6).
Exemplo: no código a seguir
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"); } } }O método
Methodgera uma exceção. A primeira ação é examinar as cláusulas delimitadorascatch, executando quaisquer filtros de exceção. Em seguida, afinallycláusula emMethodexecutes antes do controle é transferida para a cláusula correspondentecatchdelimitadora. A saída resultante é:Filter Finally Catchexemplo de fim
O try bloco de uma try instrução pode ser acessado se a try instrução estiver acessível.
Um catch bloco de uma try instrução pode ser acessado se a try instrução estiver acessível.
O finally bloco de uma try instrução pode ser acessado se a try instrução estiver acessível.
O ponto final de uma try instrução é alcançável se ambos os itens a seguir forem verdadeiros:
- O ponto final do
trybloco é alcançável ou o ponto final de pelo menos umcatchbloco é alcançável. - Se um
finallybloco estiver presente, o ponto final dofinallybloco será alcançável.
13.12 As declarações verificadas e não verificadas
As checked instruções and unchecked são usadas para controlar o contexto de verificação de estouro para operações aritméticas de tipo integral e conversões.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
A checked instrução faz com que todas as expressões no bloco sejam avaliadas em um contexto verificado e a unchecked instrução faz com que todas as expressões no bloco sejam avaliadas em um contexto não verificado.
As checked instruções and unchecked são precisamente equivalentes aos checked operadores and unchecked (§12.8.20), exceto que operam em blocos em vez de expressões.
13.13 A instrução de bloqueio
A lock instrução obtém o bloqueio de exclusão mútua para um determinado objeto, executa uma instrução e, em seguida, libera o bloqueio.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
A expressão de uma lock instrução deve denotar um valor de um tipo conhecido como uma referência. Nenhuma conversão de boxing implícita (§10.2.9) é executada para a expressão de uma lock instrução e, portanto, é um erro de tempo de compilação para a expressão denotar um valor de um value_type.
Uma lock declaração do formulário
lock (x) …
onde x é uma expressão de um reference_type, é precisamente equivalente a:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
exceto que x é avaliado apenas uma vez.
Enquanto um bloqueio de exclusão mútua é mantido, o código em execução no mesmo thread de execução também pode obter e liberar o bloqueio. No entanto, o código em execução em outros threads é impedido de obter o bloqueio até que o bloqueio seja liberado.
13.14 A instrução using
13.14.1 Geral
A using instrução obtém um ou mais recursos, executa uma instrução e, em seguida, descarta o 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
;
Um tipo de recurso é um struct de classe ou não ref que implementa ou ambos ou System.IDisposableSystem.IAsyncDisposable interfaces, que inclui um único método sem parâmetros chamado Dispose e/ou DisposeAsync; ou um struct ref que inclui um método nomeado Dispose com a mesma assinatura que o declarado por .System.IDisposable O código que está usando um recurso pode chamar Dispose ou DisposeAsync indicar que o recurso não é mais necessário.
Se a forma de resource_acquisition for local_variable_declaration , o tipo do local_variable_declaration será um dynamic tipo de recurso ou um tipo de recurso. Se a forma de resource_acquisition for expressão , essa expressão terá um tipo de recurso. Se await estiver presente, o tipo de recurso deverá implementar System.IAsyncDisposable. Um ref struct tipo não pode ser o tipo de recurso de uma using instrução com o await modificador.
As variáveis locais declaradas em um resource_acquisition são somente leitura e devem incluir um inicializador. Um erro de tempo de compilação ocorre se a instrução incorporada tentar modificar essas variáveis locais (por meio de atribuição ou dos ++ operadores and -- ), obter o endereço delas ou passá-las como parâmetros de referência ou saída.
Uma using declaração é traduzida em três partes: aquisição, uso e descarte. O uso do recurso é implicitamente incluído em uma try instrução que inclui uma finally cláusula. Essa finally cláusula descarta o recurso. Se a expressão de aquisição for avaliada nullcomo, nenhuma chamada para Dispose (ou DisposeAsync) será feita e nenhuma exceção será gerada. Se o recurso for do tipo dynamic , ele será convertido dinamicamente por meio de uma conversão dinâmica implícita (§10.2.10) para IDisposable (ou IAsyncDisposable) durante a aquisição, a fim de garantir que a conversão seja bem-sucedida antes do uso e do descarte.
Uma using declaração do formulário
using (ResourceType resource = «expression» ) «statement»
corresponde a uma das três formulações possíveis. Para recursos de struct de classe e não ref, quando ResourceType é um tipo de valor não anulável ou um parâmetro de tipo com a restrição de tipo de valor (§15.2.5), a formulação é semanticamente equivalente a
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
exceto que o elenco de resource to System.IDisposable não deve causar a ocorrência de boxe.
Caso contrário, quando ResourceType for dynamic, a formulação será
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
Caso contrário, a formulação será
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Para recursos de struct ref, a única formulação semanticamente equivalente é
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
Em qualquer formulação, a resource variável é somente leitura na instrução inserida e a d variável é inacessível e invisível à instrução inserida.
Uma using declaração do formulário:
using («expression») «statement»
tem as mesmas formulações possíveis.
Quando um resource_acquisition assume a forma de um local_variable_declaration, é possível adquirir vários recursos de um determinado tipo. Uma using declaração do formulário
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
é precisamente equivalente a uma sequência de instruções aninhadas using :
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Exemplo: O exemplo abaixo cria um arquivo chamado log.txt e grava duas linhas de texto no arquivo. Em seguida, o exemplo abre o mesmo arquivo para leitura e copia as linhas de texto contidas no console.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }Como as
TextWriterclasses andTextReaderimplementam aIDisposableinterface, o exemplo pode usarusinginstruções para garantir que o arquivo subjacente seja fechado corretamente após as operações de gravação ou leitura.exemplo de fim
Quando ResourceType é um tipo de referência que implementa IAsyncDisposable. Outras formulações para await using executar substituições semelhantes do método síncrono Dispose para o método assíncrono DisposeAsync . Uma instrução await using do formulário
await using (ResourceType resource = «expression» ) «statement»
é semanticamente equivalente às formulações mostradas abaixo com IAsyncDisposable , em vez de IDisposable, DisposeAsync em vez de Dispose, e o Task retornado DisposeAsync é awaited:
await using (ResourceType resource = «expression» ) «statement»
é semanticamente equivalente a
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Observação: todas as instruções de salto (§13.10) no embedded_statement devem estar em conformidade com a forma expandida da
usinginstrução. nota final
13.14.2 Usando declaração
Uma variante sintactica da instrução using é uma declaração de uso.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Uma declaração de uso tem a mesma semântica e pode ser reescrita como a forma de aquisição de recurso correspondente da instrução using (§13.14.1), da seguinte maneira:
using «local_variable_type» «local_variable_declarators»
// statements
é semanticamente equivalente a
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
e
await using «local_variable_type» «local_variable_declarators»
// statements
é semanticamente equivalente a
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
O tempo de vida das variáveis declaradas em uma non_ref_local_variable_declaration se estende até o final do escopo no qual elas são declaradas. Essas variáveis são então descartadas na ordem inversa na qual são declaradas.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Uma declaração de uso não deve aparecer diretamente dentro de um switch_label, mas, em vez disso, pode estar dentro de um bloco dentro de um switch_label.
13.15 A declaração de rendimento
A yield instrução é usada em um bloco de iterador (§13.3) para produzir um valor para o objeto enumerador (§15.15.5) ou objeto enumerável (§15.15.6) de um iterador ou para sinalizar o fim da iteração.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield é uma palavra-chave contextual (§6.4.4) e tem um significado especial somente quando usada imediatamente antes de uma return palavra-chave OR break .
Há várias restrições sobre onde uma yield instrução pode aparecer, conforme descrito a seguir.
- É um erro em tempo de compilação que uma
yieldinstrução (de qualquer forma) apareça fora de um method_body, operator_body ou accessor_body. - É um erro de tempo de compilação para uma
yieldinstrução (de qualquer forma) aparecer dentro de uma função anônima. - É um erro em tempo de compilação para que uma
yieldinstrução (de qualquer forma) apareça nafinallycláusula de umatryinstrução. - É um erro em tempo de compilação que uma
yield returninstrução apareça em qualquer lugar em umatryinstrução que contenha qualquer catch_clauses.
Exemplo: o exemplo a seguir mostra alguns usos válidos e inválidos de
yieldinstruções.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 }exemplo de fim
Uma conversão implícita (§10.2) deve existir do tipo da expressão na instrução yield return para o tipo de retorno (§15.15.4) do iterador.
Uma yield return instrução é executada da seguinte maneira:
- A expressão fornecida na instrução é avaliada, convertida implicitamente no tipo yield e atribuída à
Currentpropriedade do objeto enumerador. - A execução do bloco do iterador é suspensa. Se a
yield returninstrução estiver dentro de um ou maistryblocos, os blocos associadosfinallynão serão executados neste momento. - O
MoveNextmétodo do objeto enumerador retornatrueao chamador, indicando que o objeto enumerador avançou com êxito para o próximo item.
A próxima chamada para o método do MoveNext objeto enumerador retoma a execução do bloco do iterador de onde ele foi suspenso pela última vez.
Uma yield break instrução é executada da seguinte maneira:
- Se a
yield breakinstrução for delimitada por um ou maistryblocos com blocos associadosfinally, o controle será inicialmente transferido para ofinallybloco da instrução maistryinterna. Quando e se o controle atingir o ponto final de umfinallybloco, o controle será transferido para ofinallybloco da próxima instrução delimitadoratry. Esse processo é repetido até que osfinallyblocos de todas as instruções delimitadorastrytenham sido executados. - O controle é retornado ao chamador do bloco do iterador. Esse é o método ou
MoveNextoDisposemétodo do objeto enumerador.
Como uma yield break instrução transfere incondicionalmente o controle para outro lugar, o ponto final de uma yield break instrução nunca é alcançável.
ECMA C# draft specification