Partilhar via


13 Declarações

13.1 Generalidades

C# oferece 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 (§23.2) e fixed_statement (§23.7) só estão disponíveis em código não seguro (§23).

O embedded_statement nonterminal é usado para instruções que aparecem dentro de outras instruções. O uso de embedded_statement em vez de instrução 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 em tempo de compilação porque uma if instrução requer um embedded_statement em vez de uma instrução para sua if ramificação. Se esse código fosse permitido, a variável i seria declarada, mas nunca poderia ser usada. Note, no entanto, que ao colocar a declaração de i num bloco, o exemplo é válido.

Exemplo final

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 imediatamente se segue à 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 final

Se uma declaração puder ser alcançada por execução, diz-se que a declaração é alcançável. Por outro lado, se não houver possibilidade de que uma declaração seja executada, a declaração é considerada inalcançável.

Exemplo: No seguinte código

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 final

Um aviso é relatado se uma instrução diferente de throw_statement, bloquear ou empty_statement estiver inacessível. Especificamente, não é um erro que uma declaração seja inalcançável.

Nota: Para determinar se uma determinada instrução ou ponto final é alcançá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.23) que controlam o comportamento das afirmações, mas os possíveis valores de expressões não constantes não são considerados. Por outras palavras, para efeitos de análise do fluxo de controlo, considera-se que uma expressão não constante de um determinado tipo tem 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 if instruçã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 valor false, a Console.WriteLine invocação é considerada inalcançável. No entanto, se i for alterado para ser uma variável local

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

A Console.WriteLine invocação é considerada alcançável, ainda que, na realidade, nunca venha a ser executada.

Nota final

O bloco de um membro da função ou de uma função anónima é sempre considerado acessível. Ao avaliar sucessivamente as regras de acessibilidade de cada declaração em um bloco, a acessibilidade de qualquer declaração pode ser determinada.

Exemplo: No seguinte código

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.WriteLine declaração de expressão é alcançável porque o bloco do F método é alcançável (§13.3).
  • O ponto final da primeira Console.WriteLine declaração de expressão é alcançável porque essa declaração é alcançável (§13.7 e §13.3).
  • A if declaração é alcançável porque o ponto final da primeira Console.WriteLine declaração de expressão é alcançável (§13.7 e §13.3).
  • A segunda Console.WriteLine instrução de expressão é alcançável porque a expressão booleana da instrução if não tem o valor constante false.

Exemplo final

Há duas situações em que é um erro em tempo de compilação para o ponto final de uma instrução ser alcançável:

  • Como a switch instrução não permite que uma seção de switch "caia" para a próxima seção de switch, é um erro em tempo de compilação para que o ponto final da lista de instruções de uma seção de switch seja acessível. Se esse erro ocorrer, normalmente é uma indicação de que uma break instrução está faltando.

  • É um erro em 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 para ser alcançável. Se esse erro ocorrer, normalmente é uma indicação de que uma return instrução está faltando (§13.10.5).

13.3 Blocos

13.3.1 Generalidades

Um bloco permite que várias instruções sejam escritas em contextos onde uma única instrução é permitida.

block
    : '{' statement_list? '}'
    ;

Um bloco consiste numa statement_list opcional (§13.3.2), encerrada por chaves. Se a lista de instruções for omitida, o bloco é considerado vazio.

Um bloco pode conter declarações (§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 é acessível 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 iteradores são usados para implementar membros da função como iteradores (§15.15). Algumas restrições adicionais se aplicam aos blocos iteradores:

  • É um erro de compilação uma instrução return aparecer num bloco iterador (mas as instruções yield return são permitidas).
  • É um erro em tempo de compilação que um bloco iterador contenha um contexto inseguro (§23.2). Um bloco iterador sempre define um contexto seguro, mesmo quando sua declaração está aninhada em um contexto inseguro.

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 declarações ocorrem nos blocos (§13.3) e em switch_block (§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 declaração em uma lista de declarações é alcançável se pelo menos uma das seguintes condições for verdadeira:

  • A declaração é a primeira declaração, e a lista de declarações em si é acessível.
  • O ponto final da instrução anterior é alcançável.
  • A instrução é uma declaraçã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 onde 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 while instruçã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 imediatamente antes do fechamento "}" de um bloco:

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

Exemplo final

13.5 Declarações rotuladas

Um labeled_statement permite que uma declaraçã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 declaração rotulada declara um rótulo com o nome dado pelo identificador. O escopo de uma etiqueta é o bloco inteiro no qual a etiqueta é declarada, incluindo quaisquer blocos aninhados. É um erro em tempo de compilação que dois rótulos com o mesmo nome tenham escopos sobrepostos.

Um rótulo pode ser referenciado por declarações goto (§13.10.4) dentro do âmbito do rótulo.

Nota: Isso significa que goto as instruções podem transferir o controle dentro e fora dos blocos, mas nunca em blocos. Nota final

As etiquetas têm o seu próprio espaço de declaração e não interferem com 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 final

A execução de uma instrução rotulada corresponde exatamente à execução da instrução após o rótulo.

Além da acessibilidade proporcionada pelo fluxo normal de controle, uma instrução rotulada é alcançável 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 esteja inacessível e a instrução rotulada esteja fora do try_statement.

13.6 Declarações

13.6.1 Generalidades

Um declaration_statement declara uma ou mais variáveis locais, uma ou mais constantes locais ou uma função local. As instruções de declaração são permitidas em blocos e blocos de comutação, 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 âmbito de declaração mais próximo que os inclui (ponto 7.3).

13.6.2 Declarações de variáveis locais

13.6.2.1 Generalidades

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 numa ambiguidade sintática entre as três categorias, que é resolvida da seguinte forma:

  • Se não houver nenhum tipo nomeado var no escopo e a entrada corresponder a implicitly_typed_local_variable_declaration, então ele é escolhido.
  • Caso contrário, se um tipo nomeado var estiver no escopo, então implicitly_typed_local_variable_declaration não é considerado como uma correspondência possível.

Dentro de uma local_variable_declaration, cada variável é introduzida por um declarador, que é um dos implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator ou ref_local_variable_declarator para variáveis locais implicitamente tipadas, explicitamente tipadas e de referência, respetivamente. 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 são processados, incluindo quaisquer expressões inicializantes, na ordem da esquerda para a direita (§9.4.4.5).

Nota: Para uma local_variable_declaration que não ocorra como for_initializer (§13.9.4) ou resource_acquisition (§13.14), esta ordem da esquerda para a direita equivale 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 numa expressão utilizando um simple_name (§12.8.4). Uma variável local deve ser definitivamente atribuída (§9.4) em cada local onde o seu valor é obtido. Cada variável local introduzida por um local_variable_declaration não é inicialmente atribuída (§9.4.3). Se um declarador tiver uma expressão inicializante, a variável local introduzida é classificada como atribuída no final do declarador (§9.4.4.5).

O âmbito de uma variável local introduzida por um local_variable_declaration é definido do seguinte modo (§7.7):

  • Se a declaração ocorrer como um for_initializer então o escopo é o for_initializer, for_condition, for_iterator e embedded_statement (§13.9.4);
  • Se a declaração ocorrer como uma resource_acquisition, nesse caso, o escopo é 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 numa posição textual que precede o seu declarador, ou dentro de qualquer expressão de inicialização no seu declarador. Dentro do escopo de uma variável local, é um erro em 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 da sua variable_reference de inicialização. O contexto ref-safe 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, 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 refvariable_reference, seu tipo é ref T? quando T é um tipo de referência não anulável, caso contrário, seu tipo é ref T. (ref_kind é descrito no §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 digitadas:

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;

As seguintes declarações de variáveis locais digitadas implicitamente estão 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 itself

Exemplo final

13.6.2.3 Declarações de variáveis locais digitadas 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 explicity_typed_local_variable_declaration introduz 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.21.2) ou inicialização de 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 explicitamente digitadas

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
    ;

O variable_reference inicializante deve ter o tipo type e atender aos mesmos requisitos que uma atribuição ref (§12.21.3).

Se ref_kind for igual a ref readonly, os identificadores que estão a ser declarados são referências a variáveis que são tratadas como somente leitura. Caso contrário, se ref_kind for ref, os identificadoresdeclarados são referências a variáveis que devem ser graváveis.

É um erro em tempo de compilação declarar uma variável local ref, ou uma variável de tipo ref struct, dentro de um método com o modificador method_modifierasync, ou num 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 local_constant_declaration especifica o tipo das constantes introduzidas pela declaração. O tipo é seguido por uma lista de constant_declarators, cada um dos 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.23) que dá o valor da constante.

O tipo e a constant_expression de uma declaração constante local devem seguir as mesmas regras que as de uma declaração de membro constante (§15.4).

O valor de uma constante local é obtido numa expressão utilizando 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 numa posição textual que precede o fim do constant_declarator.

Uma declaração 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 sobre gramática: Ao reconhecer uma local_function_body, se ambas as alternativas null_conditional_invocation_expression e expressão forem aplicáveis, deve ser escolhida a anterior. (§15.6.1)

Exemplo: Há dois casos de uso comuns para funções locais: métodos iteradores e métodos assíncronos. Nos métodos iteradores, quaisquer exceções são observadas somente ao chamar o código que enumera a sequência retornada. Em métodos assíncronos, quaisquer exceções só são observadas quando a Tarefa retornada é aguardada. O exemplo a seguir demonstra a separação da validação de parâmetros 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 final

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 uma local_function_declaration deve ser único no seu âmbito de bloco declarado, incluindo quaisquer espaços de declaração de variáveis locais circundantes. Uma consequência disso é que local_function_declaration sobrecarregados não são permitidos.

Um local_function_declaration pode incluir um async modificador (§15.14) e um unsafe modificador (§23.1). Se a declaração incluir o async modificador, o tipo de retorno deve ser void ou um «TaskType» tipo (§15.14.1). Se a declaração incluir o static modificador, a função é uma função local estática, caso contrário, é uma função local não estática. É um erro em tempo de compilação se type_parameter_list ou parameter_list contiverem atributos. Se a função local for declarada em um contexto não seguro (§23.2), a função local pode incluir código não seguro, mesmo que a declaração da função local não inclua o unsafe modificador.

Uma função local é declarada no âmbito do bloco. Uma função local não estática pode capturar variáveis do escopo que o encerra, enquanto uma função local estática não deve (portanto, não tem acesso a locais fechados, parâmetros, funções locais não estáticas ou this). É um erro em tempo de compilação se uma variável capturada é lida pelo corpo de uma função local não estática, mas não é 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 this é um tipo struct, é um erro em tempo de compilação o corpo de uma função local aceder a this. Isso é verdadeiro se o acesso for explícito (como em this.x) ou implícito (como em x onde x é um membro de instância do struct). Esta regra apenas proíbe esse acesso e não afeta se a pesquisa de membros resulta em um membro da struct.

É um erro de tempo de compilação o facto de o corpo da função local conter uma instrução goto, break, ou continue, cujo destino esteja fora do corpo da função local.

Nota: as regras acima para this e goto espelham as regras para funções anónimas no §12.19.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 em tempo de compilação que a função seja declarada lexicamente antes da declaração de uma variável usada na função local (§7.7).

É um erro em 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 que um declarado em qualquer espaço de declaração de variável local que o inclua.

Os órgãos funcionais locais estão sempre acessíveis. O ponto de extremidade de uma declaração de função local é alcançável 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 de L não seja alcançável. Como o ponto inicial de L não é alcançável, a instrução que segue o ponto de extremidade de L nã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 contém. Exemplo final

Se o tipo de argumento para uma função local for dynamic, a função a ser chamada deve ser resolvida em tempo de compilação, não em tempo de execução.

Não deve ser utilizada uma função local numa á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 a partir do escopo anexo.
  • Não deve referenciar this ou base, nem membros de instância por meio de uma referência implícita this, nem variáveis locais, parâmetros ou funções locais não estáticas do âmbito envolvente. No entanto, tudo isso é permitido em uma nameof() 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 declarações.

Nota: Em particular, expressões como x + y e x == 1, que apenas computam um valor (que será descartado), não são permitidas como declaraçõ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 Generalidades

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 declaraçã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 está associada ao precedente if lexicamente mais próximo que é permitido pela sintaxe.

Exemplo: Assim, uma if declaração do formulário

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

é equivalente a

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

Exemplo final

Uma if instrução é executada da seguinte forma:

  • O boolean_expression (§12.24) é 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, o controle será transferido para o ponto final da if instrução.
  • Se a expressão booleana produzir false e se uma else parte estiver presente, o controle será transferido para a segunda instrução incorporada. Quando e se o controle atingir o ponto final dessa instrução, o controle será transferido para o ponto final da if instrução.
  • Se a expressão booleana resultar em false e se a parte else não estiver presente, o controle será transferido para o ponto final da instrução if.

A primeira instrução incorporada de uma if instrução é alcançável 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, é alcançável se a if instrução for alcançável e a expressão booleana não tiver o valor constante true.

O ponto final de uma if instrução é alcançável se o ponto final de pelo menos uma de suas instruções incorporadas for alcançável. Além disso, o ponto final de uma if instrução sem else parte é 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 switch associado que corresponde ao valor da expressão switch.

switch_statement
    : 'switch' '(' expression ')' switch_block
    ;

switch_block
    : '{' switch_section* '}'
    ;

switch_section
    : switch_label+ statement_list
    ;

switch_label
    : 'case' pattern case_guard?  ':'
    | 'default' ':'
    ;

case_guard
    : 'when' expression
    ;

Um switch_statement consiste na palavra-chave switch, seguida por uma expressão entre parênteses (chamada expressão switch), seguida por um switch_block. O switch_block consiste em zero ou mais switch_sections, fechados em chaves. Cada switch_section consiste em uma ou mais switch_labels seguidas de um statement_list (§13.3.2). Cada switch_label contendo case tem um padrão associado (§11) em relação ao qual o valor da expressão switch é testado. Se case_guard estiver presente, a sua expressão deve ser implicitamente convertível em tipo bool e essa expressão é avaliada como uma condição adicional para que o caso seja considerado preenchido.

O tipo que rege uma switch instrução é estabelecido pela expressão switch.

  • Se o tipo da expressão switch for sbyte, byte, short, ushort, int, uint, long, ulong, char, bool, string, ou um enum_type, ou se for o tipo de valor anulável correspondente a um desses tipos, então esse é o tipo de controlo da instrução switch.
  • Caso contrário, se existir exatamente uma conversão implícita definida pelo usuário do tipo da expressão switch para um dos seguintes possíveis tipos de governo: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, ou um tipo de valor anulável correspondente a um desses tipos, então o tipo convertido é o tipo de governo da instrução switch.
  • Caso contrário, o tipo regulador da switch instrução é o tipo da expressão switch. É um erro se esse tipo não existir.

Pode haver, no máximo, um default rótulo numa switch declaração.

É um erro se o padrão de qualquer rótulo de comutador 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 na instrução switch que não têm um guardião de caso ou cujo guardião 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 final

Uma switch instrução é executada da seguinte forma:

  • A expressão switch é avaliada e convertida para o tipo dominante.
  • O controle é transferido de acordo com o valor da expressão de switch convertida:
    • O primeiro padrão lexicamente no conjunto de rótulos na mesma case instrução que corresponde ao valor da expressão switch e para o qual a expressão guard está ausente ou é avaliada como true, faz com que o controle seja transferido para a lista de switch instruções seguindo o rótulo correspondentecase.
    • Caso contrário, se um default rótulo estiver presente, o controle será transferido para a lista de instruções após o default rótulo.
    • Caso contrário, o controle é transferido para o ponto final da switch instrução.

Nota: A ordem na qual os padrões são correspondidos em tempo de execução não está definida. Um compilador tem permissão (mas não é obrigado) para corresponder padrões de forma desordenada e para reutilizar os resultados dos padrões já correspondidos para calcular o resultado da correspondência de outros padrões. No entanto, um compilador é necessário para determinar o primeiro padrão em ordem lexical que corresponde à expressão e para o qual a cláusula de proteção está ausente ou resulta em true. Nota final

Se o ponto final da lista de instruções de uma seção de switch estiver acessível, ocorrerá um erro em tempo de compilação. Isto é conhecido como a regra "sem passagem direta".

Exemplo: O exemplo

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

é válido porque nenhuma secção de alternância tem um ponto final alcançável. Ao contrário de C e C++, não é permitido que a execução de uma secção de switch "passe para" a próxima secção de switch, e o exemplo

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

resulta num erro em tempo de compilação. Quando a execução de uma secção de comutação tiver de ser seguida pela execução de outra secção de comutação, deve ser utilizada uma declaração explícita goto case ou goto default.

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

Exemplo final

São permitidos vários rótulos num switch_section.

Exemplo: O exemplo

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

é válida. O exemplo não viola a regra "no fall through" porque os rótulos case 2: e default: fazem parte do mesmo switch_section.

Exemplo final

Nota: A regra "no fall through" impede uma classe comum de bugs que ocorrem em C e C++ quando break instruções são omitidas acidentalmente. Por exemplo, as seções da instrução acima podem ser revertidas sem afetar o comportamento da instrução: switch

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

Nota final

Nota: A lista de instruções de uma seção de switch normalmente termina numa instrução break, goto case ou goto default, mas é permitida qualquer construção que torne o ponto final da lista de instruções inacessível. Por exemplo, sabe-se que uma while instrução controlada pela expressão true booleana nunca chega ao seu ponto final. Da mesma forma, uma throw ou return instrução sempre transfere o controle para outro lugar e nunca chega ao seu ponto final. Assim, o seguinte exemplo é válido:

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

Nota final

Exemplo: O tipo que rege uma switch instrução pode ser o tipo string. 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 final

Nota: Tal como os operadores de igualdade de strings (§12.12.8), a instrução switch é sensível a maiúsculas e minúsculas e executará uma seção de switch apenas se a string da expressão switch corresponder exatamente a uma constante de rótulo case. Nota final Quando o tipo regulador 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 (§13.6). O escopo de uma variável local ou constante declarada em um bloco de switch é o bloco de switch.

Um rótulo de switch pode ser acessado se pelo menos uma das seguintes opções for verdadeira:

  • A expressão switch é um valor constante ou
    • o rótulo é um case cujo 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 false;
    • É um default rótulo, e nenhuma seção de switch 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.
  • A expressão switch não é um valor constante e
    • o rótulo é um case sem guarda ou com um guarda cujo valor não é a constante 'false';
    • um rótulo default e
      • o conjunto de padrões que aparecem entre os casos da instrução do switch que não têm condições de guarda ou têm condições de guarda cujo valor é a constante verdadeira, não é exaustivo (§11.4) para o tipo que governa o switch; ou
      • O tipo de controle do 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 proteções ou têm proteções cujo valor é a constante true não contém um padrão que corresponderia ao valor null.
  • O rótulo do switch é referenciado por uma instrução goto case acessívelgoto default.

A lista de instruções de uma determinada seção de switch é acessível se a instrução switch for acessível e a seção de switch contiver um rótulo de switch acessível.

O ponto final de uma switch instrução é alcançável se a instrução switch estiver acessível e pelo menos uma das seguintes opções for verdadeira:

  • A declaração switch contém uma declaração break acessível que sai da declaração switch.
  • Nenhum default rótulo está presente e/ou
    • A expressão switch é 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 proteções ou têm proteções cujo valor é a constante true, não é exaustivo (§11.4) para o tipo de regulador do interruptor.
    • A expressão switch é um valor não constante de um tipo anulável, e nenhum padrão que aparece entre os casos da instrução switch que não têm proteções ou têm proteções cujo valor é a constante true corresponderia ao valor null.
    • A expressão switch é um valor constante e nenhum rótulo case sem um protetor ou cujo protetor seja a constante verdadeira corresponderia a esse valor.

Exemplo: O código a seguir mostra um uso sucinto da when cláusula:

static object CreateShape(string shapeDescription)
{
   switch (shapeDescription)
   {
        case "circle":
            return new Circle(2);
        …
        case var o when string.IsNullOrWhiteSpace(o):
            return null;
        default:
            return "invalid shape description";
    }
}

O caso var corresponde a null, a cadeia de caracteres vazia ou qualquer cadeia de caracteres que contenha apenas espaço em branco. Exemplo final

13.9 Instruções de iteração

13.9.1 Generalidades

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 declaraçã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 forma:

  • O boolean_expression (§12.24) é avaliado.
  • Se a expressão booleana produzir true, o controle será transferido para a instrução incorporada. Quando e se o controlo chegar ao ponto final da instrução incorporada (possivelmente a partir da execução de uma instrução continue), o controlo é transferido para o início da instrução while.
  • Se a expressão booleana produzir false, o controle será transferido para o ponto final da while instrução.

Dentro da instrução incorporada de uma while declaração, uma break instrução (§13.10.2) pode ser usada para transferir o controle para o ponto final da while instrução (terminando 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 declaração).

A instrução incorporada de uma while instrução é atingível se a while instrução seja atingível e a expressão booleana não tem o valor constante false.

O ponto final de uma while instrução é atingível se pelo menos uma das seguintes situações for verdadeira:

  • A declaração while contém uma declaração break acessível que sai da declaração while.
  • A instrução while é alcançável e a expressão booleana não tem o valor constante true.

13.9.3 A declaração do

A do instrução condicionalmente executa uma instrução incorporada uma ou mais vezes.

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

Uma do instrução é executada da seguinte forma:

  • O controle é transferido para a instrução incorporada.
  • Quando e se o controle atingir o ponto final da declaração incorporada (possivelmente a partir da execução de uma continue declaração), o boolean_expression (§12.24) é avaliado. Se a expressão booleana render true, o controlo será transferido para o início da declaração do. Caso contrário, o controle é transferido para o ponto final da do instrução.

Dentro da instrução incorporada de uma do declaração, uma break instrução (§13.10.2) pode ser usada para transferir o controle para o ponto final da do instrução (terminando 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 declaração).

A instrução incorporada de uma do instrução é acessível se a do instrução for alcançável.

O ponto final de uma do instrução é atingível se pelo menos uma das seguintes situações for verdadeira:

  • A declaração do contém uma declaração break acessível que sai da declaração do.
  • O ponto final da instrução incorporada é alcançável e a expressão booleana não tem o valor trueconstante .

13.9.4 O para declaração

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

O for_initializer, se existir, consiste num local_variable_declaration (§13.6.2) ou numa 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 existir, será um boolean_expression (§12.24).

O for_iterator, se existir, consiste numa lista de statement_expressions (§13.7) separados por vírgulas.

Uma for instrução é executada da seguinte forma:

  • Se um for_initializer estiver presente, os inicializadores de variáveis ou expressões de instrução serão executados na ordem em que são gravados. Esta etapa é executada apenas uma vez.
  • Se estiver presente um for_condition , este é avaliado.
  • Se o for_condition não estiver presente ou se a avaliação resultar em true, o controlo é transferido para a instrução incorporada. Quando e se o controle atinge o ponto final da instrução incorporada (possivelmente a partir da execução de uma continue instrução), as expressões do for_iterator, se houver, são avaliadas em sequência e, em seguida, outra iteração é executada, começando com a avaliação do for_condition na etapa acima.
  • Se o for_condition estiver presente e a avaliação é bem-sucedida, o resultado false transferirá o controle para o ponto final da instrução for.

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 de término da for instrução (terminando 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 de término da instrução incorporada (executando assim o for_iterator e realizando outra iteração da for instrução, começando pelo for_condition).

A instrução incorporada de uma instrução for é alcançável se uma das seguintes condições for verdadeira:

  • A for declaração está acessível e nenhum for_condition está presente.
  • A instrução for é alcançável e uma for_condition está presente e não tem o valor false constante.

O ponto final de uma for instrução é atingível se pelo menos uma das seguintes situações for verdadeira:

  • A declaração for contém uma declaração break acessível que sai da declaração for.
  • A instrução for é alcançável e uma for_condition está presente e não tem o valor true constante.

13.9.5 A declaração foreach

13.9.5.1 Generalidades

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
    ;

A 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 é dado como o local_variable_type, e nenhum tipo nomeado var está no escopo, a variável de iteração é dita como sendo uma variável de iteração digitada implicitamente, e seu tipo é tomado como o tipo de elemento da foreach instrução, conforme especificado abaixo.

É um erro de tempo de compilação para ambos await e ref_kind estarem presentes num foreach statement.

Se o foreach_statement contiver ambos ou nenhum ref e readonly, a variável de iteração denotará 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, ocorre um erro em tempo de compilação se a instrução incorporada tentar modificá-la (via atribuição ou os operadores ++ e --) ou passá-la como um parâmetro de referência ou de saída.

O processamento em tempo de compilação de uma foreach instrução primeiro determina o tipo de coleção, o tipo de enumerador e o tipo de iteração da expressão. O processamento de uma foreach declaração é detalhado no §13.9.5.2 e o processo para uma await foreach é detalhado no §13.9.5.3.

Nota: Se a expressão tiver o valor null, a System.NullReferenceException é lançado em tempo de execução. Nota final

Uma implementação é permitida para implementar um determinado foreach_statement de forma diferente; por exemplo, por razões de desempenho, desde que o comportamento seja consistente com a expansão acima.

13.9.5.2 Síncrono para cada

O processamento em tempo de compilação de uma foreach instrução primeiro determina o tipo de coleção, o tipo de enumerador e o tipo de iteração da expressão. Esta determinação procede da seguinte forma:

  • Se o tipo X de expressão é um tipo de matriz, então há uma conversão de referência implícita de X para a IEnumerable interface (uma vez que System.Array implementa esta interface). O tipo de coleção é a interface IEnumerable, o tipo de enumerador é a interface IEnumerator e o tipo de iteração é o tipo de elemento do tipo de matriz X.
  • Se o tipo X da expressão for dynamic então existe uma conversão implícita da expressão para a interface IEnumerable (§10.2.10). O tipo de coleção é a IEnumerable interface e o tipo de enumerador é a IEnumerator interface. Se o var identificador é dado como o local_variable_type então o tipo de iteração é dynamic, caso contrário, é object.
  • Caso contrário, determine se o tipo X tem um método de GetEnumerator apropriado:
    • Realizar uma pesquisa de membros no tipo X com o identificador GetEnumerator e sem argumentos 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étodo, verifique se há uma interface enumerável conforme descrito abaixo. Recomenda-se que um aviso seja emitido se a pesquisa de membros resultar em algo que não seja um grupo de métodos ou nenhuma correspondência encontrada.
    • 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 nenhum método aplicável, 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. Recomenda-se que seja emitido um aviso caso a resolução de sobrecarga produza algo diferente de um método de instância pública inequívoco ou caso não haja métodos aplicáveis.
    • Se o tipo de retorno E do método GetEnumerator não for um tipo de classe, struct ou interface, um erro será produzido e nenhuma outra etapa será executada.
    • A pesquisa de membros é realizada em E com o identificador Current e sem argumentos de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, ou se o resultado for qualquer coisa que não seja uma propriedade de instância pública que permita a leitura, será gerado um erro e nenhuma outra etapa será executada.
    • A pesquisa de membros é realizada em E com o identificador MoveNext e sem argumentos de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, o resultado for um erro ou o resultado for qualquer coisa, exceto um grupo de métodos, um erro será produzido e nenhuma outra etapa será executada.
    • A resolução de sobrecarga é executada no grupo de métodos com uma lista de argumentos vazia. Se a resolução de sobrecarga não resultar em nenhuns métodos aplicáveis, resultar em uma ambiguidade, ou resultar em um único melhor método, mas se esse for estático ou não público, ou seu tipo de retorno não for bool, um erro será produzido e nenhuma outra etapa será tomada.
    • O tipo de coleção é X, o tipo de enumerador é Ee o tipo de iteração é o tipo da propriedade Current. A Current propriedade pode incluir o ref modificador, caso em que a expressão retornada é um variable_reference (§9.5) que é opcionalmente somente leitura.
  • 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 de X para IEnumerable<Tᵢ>, há um tipo T único tal que T não é dynamic e para todos os outros Tᵢ há uma conversão implícita de IEnumerable<T> para IEnumerable<Tᵢ>, então o tipo de coleção é a interface IEnumerable<T>, o tipo de enumerador é a interface IEnumerator<T>, e o tipo de iteração é T.
    • Caso contrário, se houver mais de um tipo T, então um erro é gerado e nenhuma outra ação é realizada.
    • Caso contrário, se houver uma conversão implícita de X para a interface System.Collections.IEnumerable, então o tipo de coleção é essa interface, o tipo de enumerador é a interface System.Collections.IEnumerator, e o tipo de iteração é object.
    • Caso contrário, um erro é produzido e nenhuma outra medida é tomada.

As etapas acima, se bem-sucedidas, produzem inequivocamente um tipo Cde coleção, tipo E de enumerador e tipo Tde iteração, ref Tou ref readonly T. 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 à expressão x ou à instrução incorporada ou a 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 T (o tipo de iteração) para V (o tipo de variável_local na declaração), um erro é produzido e nenhuma etapa adicional é realizada.

Quando a variável de iteração é uma variável de referência (§9.7), uma declaração na forma de foreach

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 à expressão x ou à instrução incorporada ou a qualquer outro código-fonte do programa. A variável v de referência é leitura-escrita na instrução incorporada, mas v não deve ser reatribuída (§12.21.3). Se não houver uma conversão de identidade (§10.2.2) do T (o tipo de iteração) para V (o local_variable_type na declaração foreach), ocorre um erro e nenhuma outra etapa é realizada.

Uma foreach instrução da forma foreach (ref readonly V v in x) «embedded_statement» tem uma forma equivalente semelhante, mas a variável de referência v está ref readonly na instrução incorporada e, portanto, não pode ser reatribuída como referência nem alterada.

A colocação do v dentro do loop while é importante para a forma como é capturado (§12.19.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 v na forma expandida fosse declarado fora do while loop, ele seria compartilhado entre todas as iterações, e seu valor após o for loop seria o valor final, 13que é o que a invocação de f imprimiria. Em vez disso, como cada iteração tem sua própria variável v, a capturada na f primeira iteração continuará a manter o valor 7, que é o que será impresso. (Observe que as versões anteriores do C# declararam v fora do loop while.)

Exemplo final

O corpo do bloco de finally é construído de acordo com as seguintes etapas:

  • Se houver uma conversão implícita de E para a System.IDisposable interface, então

    • Se E for um tipo de valor não anulável, a cláusula finally será expandida para o equivalente semântico de:

      finally
      {
          ((System.IDisposable)e).Dispose();
      }
      
    • Caso contrário, a finally cláusula é expandida para o equivalente semântico de:

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

      exceto que, se E for um tipo de valor, ou um parâmetro de tipo instanciado para um tipo de valor, então a conversão de e para System.IDisposable não deve causar o encaixotamento.

  • Caso contrário, se E for um tipo selado, a finally cláusula é expandida para um bloco vazio:

    finally {}
    
  • Caso contrário, a finally cláusula é alargada 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 qualquer código de usuário. Em particular, não entra em conflito com qualquer outra variável cujo âmbito inclua o finally bloco.

A ordem em que foreach atravessa os elementos de uma matriz, é a seguinte: Para matrizes unidimensionais, os elementos são percorridos em ordem de índice crescente, começando com o índice 0 e terminando com o índice Length – 1. Para matrizes multidimensionais, os elementos são percorridos de tal forma que os índices da dimensão mais à direita são aumentados primeiro, depois a dimensão imediatamente à esquerda, e assim sucessivamente.

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 produção é a seguinte:

1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9

Exemplo final

Exemplo: No exemplo a seguir

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

o tipo de n é inferido como sendo int, o tipo de iteração de numbers.

Exemplo final

13.9.5.3 aguardar

O processamento em tempo de compilação de uma foreach instrução primeiro determina o tipo de coleção, o tipo de enumerador e o tipo de iteração da expressão. O processamento de uma foreach declaração é detalhado no §13.9.5.2 e o processo para uma await foreach é detalhado no §13.9.5.3.

Esta determinação procede da seguinte forma:

  • Determine se o tipo X tem um método apropriado GetAsyncEnumerator :
    • Realizar uma pesquisa de membros no tipo X com o identificador GetAsyncEnumerator e sem argumentos 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étodo, verifique se há uma interface enumerável conforme descrito abaixo. Recomenda-se que um aviso seja emitido se a pesquisa de membros resultar em algo que não seja um grupo de métodos ou nenhuma correspondência encontrada.
    • 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 nenhum método aplicável, 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. Recomenda-se que seja emitido um aviso caso a resolução de sobrecarga produza algo diferente de um método de instância pública inequívoco ou caso não haja métodos aplicáveis.
    • Se o tipo de retorno E do método GetAsyncEnumerator não for um tipo de classe, struct ou interface, um erro será produzido e nenhuma outra etapa será executada.
    • A pesquisa de membros é realizada em E com o identificador Current e sem argumentos de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, ou se o resultado for qualquer coisa que não seja uma propriedade de instância pública que permita a leitura, será gerado um erro e nenhuma outra etapa será executada.
    • A pesquisa de membros é realizada em E com o identificador MoveNextAsync e sem argumentos de tipo. Se a pesquisa de membro não produzir nenhuma correspondência, o resultado for um erro ou o resultado for qualquer coisa, exceto um grupo de métodos, um erro será produzido e nenhuma outra etapa será executada.
    • A resolução de sobrecarga é executada no grupo de métodos com uma lista de argumentos vazia. Se a resolução de sobrecarga não resultar em nenhum método aplicável, resultar em uma ambiguidade ou resultar em um único melhor método, mas esse método for estático ou não público, ou seu tipo de retorno não for esperado (§12.9.8.2) onde o await_expression é classificado como a bool (§12.9.8.3), um erro é produzido e nenhuma outra etapa é tomada.
    • O tipo de coleção é X, o tipo de enumerador é Ee o tipo de iteração é o tipo da propriedade Current.
  • Caso contrário, verifique se há uma interface enumerável assíncrona:
    • Se entre todos os tipos Tᵢ para os quais há uma conversão implícita de X para IAsyncEnumerable<Tᵢ>, há um tipo T único tal que T não é dynamic e para todos os outros Tᵢ há uma conversão implícita de IAsyncEnumerable<T> para IAsyncEnumerable<Tᵢ>, então o tipo de coleção é a interface IAsyncEnumerable<T>, o tipo de enumerador é a interface IAsyncEnumerator<T>, e o tipo de iteração é T.
    • Caso contrário, se houver mais de um tipo T, então um erro é gerado e nenhuma outra ação é realizada.
    • Caso contrário, um erro é produzido e nenhuma outra medida é tomada.

As etapas acima, se bem-sucedidas, produzem inequivocamente um tipo de coleção C, tipo de enumerador E e tipo de iteração T. Uma await foreach declaração do formulário

await foreach (V v in x) «embedded_statement»

é então equivalente a:

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

A variável e não é visível ou acessível à expressão x ou à instrução incorporada ou a 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 T (o tipo de iteração) para V (o tipo de variável_local na declaração), um erro é produzido e nenhuma etapa adicional é realizada.

Um enumerador assíncrono pode, opcionalmente, expor um método DisposeAsync que pode ser invocado sem argumentos e que retorna algo que possa ser await e cujo GetResult() retorne void.

Uma foreach declaração do formulário

await foreach (T item in enumerable) «embedded_statement»

é alargado para:

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

13.10 Instruções de salto

13.10.1 Generalidades

As instruções de salto transferem o controlo de forma incondicional.

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 de salto ocorre dentro de um bloco, e o alvo dessa instrução de salto está fora desse bloco, a instrução de salto é dita para sair do bloco. Embora uma instrução de salto possa transferir o controle para fora de um bloco, ela nunca pode transferir o controle para dentro de um bloco.

A execução de instruções de salto é dificultada pela presença de instruções intervenientes try. Na ausência de tais try instruções, uma instrução de salto transfere incondicionalmente o controlo de si própria para o seu respetivo alvo. Na presença de tais declarações intervenientes try , a execução é mais complexa. Se a instrução jump sair de um ou mais try blocos com os blocos associados finally , o controle será inicialmente transferido para finally bloco da instrução try mais interna. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções intervenientes try tenham sido executados.

Exemplo: No seguinte código

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 finally blocos associados a duas try instruções são executados antes que o controle seja transferido para o destino da instrução Jump. A produção é a seguinte:

Before break
Innermost finally block
Outermost finally block
After break

Exemplo final

13.10.2 A declaração de interrupção

A instrução break sai da instrução switch, while, do, for ou foreach mais próxima.

break_statement
    : 'break' ';'
    ;

O destino de uma instrução break é o ponto final da instrução mais próxima que encerra uma instrução switch, while, do, for ou foreach. Se uma instrução break não for incluída em uma instrução switch, while, do, for ou foreach, ocorrerá um erro em tempo de compilação.

Quando várias switch, while, do, for ou foreach instruções são aninhadas umas nas outras, uma break instrução aplica-se apenas à instrução mais interna. Para transferir o controlo através de vários níveis de nidificação, deve ser utilizada uma goto declaração (§13.10.4).

Uma break declaração não pode sair de um finally bloco (§13.11). Quando uma break instrução ocorre dentro de um finally bloco, o alvo da instrução deve estar dentro do mesmo break bloco, caso contrário, ocorre um erro em tempo de finally compilação.

Uma break instrução é executada da seguinte forma:

  • Se a break instrução sair de um ou mais try blocos associados finally, o controle será inicialmente transferido para o finally bloco da instrução mais interna try. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções intervenientes try tenham sido executados.
  • O controle é transferido para o break alvo da declaraçã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 declaração de continuação

A instrução continue inicia uma nova iteração da instrução mais próxima entre while, do, for, ou foreach.

continue_statement
    : 'continue' ';'
    ;

O destino de uma continue instrução é o ponto final da instrução incorporada da declaração anexa while, do, for, ou foreach instrução mais próxima. Se uma continue instrução não estiver incluída por uma while, do, for ou foreach instrução, ocorrerá um erro em tempo de compilação.

Quando várias while, do, for, ou foreach instruções são aninhadas umas nas outras, uma continue instrução aplica-se apenas à instrução mais interna. Para transferir o controlo através de vários níveis de nidificação, deve ser utilizada uma goto declaração (§13.10.4).

Uma continue declaração não pode sair de um finally bloco (§13.11). Quando uma continue instrução ocorre dentro de um finally bloco, o alvo da instrução deve estar dentro do mesmo continue bloco, caso contrário, ocorre um erro em tempo de finally compilação.

Uma continue instrução é executada da seguinte forma:

  • Se a continue instrução sair de um ou mais try blocos associados finally, o controle será inicialmente transferido para o finally bloco da instrução mais interna try. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções intervenientes try tenham sido executados.
  • O controle é transferido para o continue alvo da declaraçã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 declaraçã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 alvo de uma instrução gotoidentificador é a instrução rotulada com o rótulo fornecido. Se um rótulo com o nome especificado não existir no membro da função atual, ou se a instrução goto não estiver dentro do escopo do rótulo, ocorrerá um erro em tempo de compilação.

Nota: Esta regra permite o uso de uma goto instrução para transferir o controle de um escopo aninhado, mas não para um escopo aninhado. No exemplo

class Test
{
    static void Main(string[] args)
    {
        string[,] table =
        {
            {"Red", "Blue", "Green"},
            {"Monday", "Wednesday", "Friday"}
        };
        foreach (string str in args)
        {
            int row, colm;
            for (row = 0; row <= 1; ++row)
            {
                for (colm = 0; colm <= 2; ++colm)
                {
                    if (str == table[row,colm])
                    {
                        goto done;
                    }
                }
            }
            Console.WriteLine($"{str} not found");
            continue;
          done:
            Console.WriteLine($"Found {str} at [{row}][{colm}]");
        }
    }
}

Uma goto instrução é usada para transferir o controle para fora de um escopo aninhado.

Nota final

O alvo de uma goto case declaração é a lista de instruções na declaração imediatamente anexa switch (§13.8.3) que contém um case rótulo com um padrão constante do valor constante dado e sem proteção. Se a instrução goto case não for delimitada por uma instrução switch, se a instrução delimitadora switch mais próxima não contiver tal case, ou se a constant_expression não for implicitamente convertível (§10.2) para o tipo regulador da instrução delimitadora switch mais próxima, ocorrerá um erro em tempo de compilação.

O alvo de uma goto default declaração é a lista de declarações na declaração imediatamente anexa switch (§13.8.3), que contém um default rótulo. Se a instrução goto default não estiver envolvida por uma instrução switch, ou se a instrução switch envolvente mais próxima não contiver um rótulo default, é produzido um erro em tempo de compilação.

Uma goto declaração não pode sair de um finally bloco (§13.11). Quando uma goto instrução ocorre dentro de um finally bloco, o alvo da instrução deve estar dentro do mesmo goto bloco, ou então ocorre um erro em tempo de finally compilação.

Uma goto instrução é executada da seguinte forma:

  • Se a goto instrução sair de um ou mais try blocos associados finally, o controle será inicialmente transferido para o finally bloco da instrução mais interna try. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções intervenientes try tenham sido executados.
  • O controle é transferido para o goto alvo da declaraçã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 Declaração de devolução

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; Uma que contém refexpressão é chamada de retorno por ref, e uma que contém apenas expressão é chamada de retorno por valor.

É um erro em tempo de compilação usar um retorno-sem-valor de um método declarado como sendo retorno-por-valor ou retorno-por-referência (§15.6.1).

É um erro em tempo de compilação usar um return-by-ref de um método declarado como sendo returns-no-value ou returns-by-value.

É um erro em tempo de compilação usar um retorno por valor de um método declarado como não retornando valor ou retornando por referência.

É um erro em tempo de compilação usar um return-by-ref se a expressão não for uma variable_reference ou for uma referência a uma variável cujo contexto seguro de referência (ref-safe-context) não é o contexto do chamador (caller-context) (§9.7.2).

É um erro em tempo de compilação usar uma devolução por referência de um método declarado com o method_modifierasync.

Diz-se que um membro da função calcula um valor se for um método com um método que retorna por valor (§15.6.11), um acessor que retorna por valor de uma propriedade ou indexador, ou um operador definido pelo utilizador. Os membros da função que são retornos sem valor não calculam um valor e são métodos com o tipo voidde retorno efetivo, definem acessadores de propriedades e indexadores, adicionam e removem acessadores de eventos, construtores de instância, construtores estáticos e finalizadores. Os membros da função que retornam por referência 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 referência, 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 funções anónimas (§12.19) e participar na identificação de quais conversões existem para essas funções (§10.7.1).

É um erro em tempo de compilação para uma return instrução aparecer em um finally bloco (§13.11).

Uma return instrução é executada da seguinte forma:

  • Para um retorno por valor, a expressão é avaliada e seu valor é convertido para o tipo de retorno efetivo da função que contém por uma conversão implícita. O resultado da conversão torna-se o valor de 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 return-by-ref do método envolvente incluir readonly, a variável resultante será só de leitura.
  • Se a return instrução estiver delimitada por um ou mais blocos try ou catch com blocos associados finally, o controle será inicialmente transferido para o bloco finally da instrução try mais interna. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções anexas try tenham sido executados.
  • Se a função de contenção não for uma função assíncrona, o controle será retornado ao chamador da função de contenção, juntamente 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 declaração de lançamento

A throw declaração lança uma exceção.

throw_statement
    : 'throw' expression? ';'
    ;

Uma throw instrução com uma expressão lança uma exceção produzida pela avaliação da expressão. A expressão deve ser implicitamente convertí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, um System.NullReferenceException é lançado em alternativa.

Uma throw instrução sem expressão só pode ser usada num catch bloco, caso em que essa instrução relança a exceção que está a ser tratada atualmente 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 controlo é transferido para a primeira catch cláusula numa declaração envolvente try que pode lidar com a exceção. O processo que ocorre do ponto em que a exceção está sendo lançada até o ponto de transferência de 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. Este comportamento é especificado em (§21.4).

  • No membro da função atual, cada try instrução que encerra o ponto de lançamento é examinada. Para cada afirmação S, começando com a declaração mais try interna e terminando com a declaração mais try externa, as seguintes etapas são avaliadas:

    • Se o bloco try abrange o ponto de lançamento S e se S tem uma ou mais cláusulas catch, as cláusulas catch são examinadas por ordem de aparecimento para localizar um manipulador adequado para a exceção. A primeira catch cláusula que especifica um tipo de exceção T (ou um parâmetro de tipo que, em tempo de execução, denota um tipo de exceção T) de tal forma que o tipo em tempo de execução de E deriva de T é considerada 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 uma catch cláusula contém um filtro de exceção, essa catch cláusula é considerada uma correspondência se o filtro de exceção for avaliado como true. Uma cláusula geral catch (§13.11) é considerada uma correspondência para qualquer tipo de exceção. Se uma cláusula correspondente catch estiver localizada, a propagação da exceção será concluída transferindo o controle para o bloco dessa catch cláusula.
    • Caso contrário, se o bloco try ou um bloco catch de S encerrar o ponto de lançamento e se S tiver um bloco finally, o controle é transferido para o bloco finally. Se o bloco finally lançar outra exceção, o processamento da exceção atual será encerrado. Caso contrário, quando o controle atingir o ponto final do bloco finally, o processamento da exceção atual será continuado.
  • Se um manipulador de exceção não foi localizado na chamada de função atual, a invocação de função é encerrada e uma das seguintes situações ocorre:

    • 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 a partir 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 é colocada em um estado com defeito ou cancelado, conforme descrito no §15.14.3.

    • Se a função atual for assíncrona e retornar void, o contexto de sincronização do thread atual será notificado conforme o descrito na §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 próprio thread será encerrado. O impacto dessa rescisão é definido pela implementação.

13.11 A declaração de tentativa

A instrução try 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 deixa a 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, em seguida, um finally_clause opcional. Deve haver pelo menos um catch_clause ou um finally_clause.

Num exception_specifier o tipo, ou a sua classe de base efetiva, se for um type_parameter, deve ser System.Exception ou um tipo que dele derive.

Quando uma catch cláusula especifica um class_type e um identificador, uma variável de exceção do nome e do 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_filter e catch do bloco, 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 definitivamente atribuída em todo o seu escopo.

A menos que uma catch cláusula inclua um nome de variável de exceção, é impossível aceder ao objeto de exceção no filtro e no bloco catch.

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 estiver presente, será a última catch cláusula.

Nota: Algumas linguagens de programação podem suportar exceções que não são representáveis como um objeto derivado do System.Exception, embora tais exceções nunca possam ser geradas pelo código C#. Poderá ser utilizada uma cláusula geral catch para abranger essas exceções. Assim, uma cláusula geral catch é semanticamente diferente daquela que especifica o tipo System.Exception, na medida em que a primeira também pode apanhar exceções de outras línguas. Nota final

A fim de localizar um manipulador para uma exceção, as cláusulas catch são examinadas em ordem lexical. Se uma cláusula catch especifica um tipo, mas nenhum filtro de exceção, é um erro de compilação para uma cláusula catch posterior da mesma instrução try especificar um tipo que seja o mesmo ou derivado desse tipo.

Nota: Sem esta restrição, seria possível escrever cláusulas inalcançáveis catch . Nota final

Dentro de um catch bloco, uma throw instrução (§13.10.6) sem expressão pode ser usada para relançar 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 é relançada.

Exemplo: No seguinte código

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 F captura uma exceção, grava algumas informações de diagnóstico no console, altera a variável de exceção e lança novamente a exceção. A exceção que é relançada é a exceção original, portanto, a saída produzida é:

Exception in F: G
Exception in Main: G

Se o primeiro catch bloco tivesse lançado e em vez de relançar a exceção atual, a saída produzida seria a seguinte:

Exception in F: G
Exception in Main: F

Exemplo final

É um erro em tempo de compilação para uma instrução break, continue, ou goto transferir o controlo para fora de um bloco finally. Quando uma declaração break, continue, ou goto ocorre num bloco finally, o alvo da declaração deve estar no mesmo bloco finally, caso contrário, ocorre um erro de compilação.

É um erro em tempo de compilação para uma return instrução ocorrer em um finally bloco.

Quando a execução atinge uma try declaração, o controle é transferido para o try bloco. Se o controlo atingir o ponto final do bloco try sem que uma exceção seja propagada, o controlo será transferido para o bloco finally, se existir. Se nenhum finally bloco existir, o controle será transferido para o ponto final da try instrução.

Se uma exceção tiver sido 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 circundantes, conforme descrito no §13.10.6. Uma catch cláusula é válida se o tipo de exceção corresponder a qualquer exception_specifier e qualquer exception_filter for verdadeiro. 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 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 controlo atingir o ponto final do bloco catch sem que uma exceção seja propagada, o controlo será transferido para o bloco finally, se existir. Se nenhum finally bloco existir, o controle será transferido para o ponto final da try instrução. Se uma exceção tiver sido propagada a partir do bloco catch, o controlo será transferido para o bloco finally se este existir. A exceção é propagada para a próxima instrução anexa 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 anexa 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 ocorre 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 controlo chegar ao ponto final do bloco finally sem que uma exceção seja propagada, o controlo será transferido para o ponto final da instrução try.

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 detalhadamente na descrição da throw declaração (§13.10.6).

Exemplo: No seguinte código

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 Method lança uma exceção. A primeira ação é examinar as cláusulas anexas catch , executando quaisquer filtros de exceção. Em seguida, a cláusula em finally executa antes que o Method controle seja transferido para a cláusula de correspondência catch anexa. A saída resultante é:

Filter
Finally
Catch

Exemplo final

O bloco try de uma declaração try é acessível se a declaração try for acessível.

Um catch bloco de uma try declaração é acessível se for possível aceder à try declaração.

O bloco finally de uma declaração try é acessível se a declaração try for acessível.

O ponto final de uma try instrução é alcançável se ambos os itens a seguir forem verdadeiros:

  • O ponto final do try bloco é alcançável ou o ponto final de pelo menos um catch bloco é alcançável.
  • Se um finally bloco estiver presente, o ponto final do finally bloco será alcançável.

13.12 As declarações verificadas e não verificadas

As checked instruções e unchecked são usadas para controlar o contexto de verificação de estouro para operações aritméticas e conversões do tipo integral.

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 instruções checked e unchecked são precisamente equivalentes aos operadores checked e unchecked, exceto pelo fato de que operam em blocos em vez de em expressões.

13.13 A declaraçã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 declaração deve indicar um valor de um tipo conhecido como referência. Nenhuma conversão implícita de boxe (§10.2.9) é realizada para a expressão de uma lock instrução e, portanto, é um erro em 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);
    }
}

Só que x só é avaliada uma vez.

Enquanto um bloqueio de exclusão mútua é mantido, o código executado no mesmo thread de execução também pode obter e liberar o bloqueio. No entanto, o código executado em outros threads é impedido de obter o bloqueio até que o bloqueio seja liberado.

13.14 A declaração de utilização

A using instrução obtém um ou mais recursos, executa uma instrução e, em seguida, descarta o recurso.

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

resource_acquisition
    : local_variable_declaration
    | expression
    ;

Um recurso é uma classe ou struct que implementa a interface System.IDisposable (IAsyncDisposable para fluxos assíncronos), que inclui um único método sem parâmetros chamado Dispose (DisposeAsync para fluxos assíncronos). O código que está usando um recurso pode chamar Dispose para indicar que o recurso não é mais necessário.

Se a forma de resource_acquisition for local_variable_declaration, então o tipo do local_variable_declaration deve ser um dynamic, ou um tipo que possa ser convertido implicitamente para System.IDisposable (IAsyncDisposable para fluxos assíncronos). Se a forma de resource_acquisition é expressão , então esta expressão deve ser implicitamente convertível em System.IDisposable (IAsyncDisposable para fluxos assíncronos).

As variáveis locais declaradas num resource_acquisition são somente leitura e devem incluir um inicializador. Um erro em tempo de compilação ocorre se a instrução incorporada tentar modificar essas variáveis locais (via atribuição ou os operadores ++ e --), tomar 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 alienação. O uso do recurso é implicitamente incluído numa try instrução que inclui uma finally cláusula. Esta finally cláusula dispõe do recurso. Se um null recurso for adquirido, nenhuma chamada para Dispose (DisposeAsync para fluxos assíncronos) será feita e nenhuma exceção será lançada. Se o recurso for do tipo dynamic , ele é convertido dinamicamente por meio de uma conversão dinâmica implícita (§10.2.10) para IDisposable (IAsyncDisposable para fluxos assíncronos) durante a aquisição, a fim de garantir que a conversão seja bem-sucedida antes do uso e eliminação.

Uma using declaração do formulário

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

corresponde a uma das três expansões possíveis. 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 expansã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 fazer com que o boxe ocorra.

Caso contrário, quando ResourceType é dynamic, a expansão é

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

Caso contrário, a expansão é

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

Em qualquer expansão, a resource variável é somente leitura na instrução incorporada, e a d variável é inacessível e invisível para a instrução incorporada.

Uma implementação é permitida para implementar um determinado using_statement de forma diferente, por exemplo, por razões de desempenho, desde que o comportamento seja consistente com a expansão acima.

Uma using declaração do formulário:

using («expression») «statement»

tem as mesmas três expansões possíveis. Neste caso ResourceType é implicitamente o tipo de tempo de compilação da expressão, se tiver um. Caso contrário, a própria interface IDisposable (IAsyncDisposable para fluxos assíncronos) é usada como o ResourceType. A resource variável é inacessível e invisível para a instrução incorporada.

Quando um resource_acquisition assume a forma de um local_variable_declaration, é possível adquirir múltiplos 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 esse mesmo arquivo para leitura e copia as linhas de texto contidas para o 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 TextWriter classes e TextReader implementam a IDisposable interface, o exemplo pode usar using instruções para garantir que o arquivo subjacente seja fechado corretamente após as operações de gravação ou leitura.

Exemplo final

13.15 A demonstração de rendimentos

A yield instrução é usada em um bloco 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 apenas quando utilizada imediatamente antes de uma return ou break palavra-chave.

Há várias restrições quanto a onde uma yield instrução pode aparecer, como descrito a seguir.

  • É um erro em tempo de compilação para uma yield instrução (de qualquer forma) aparecer fora de um method_body, operator_body ou accessor_body.
  • É um erro em tempo de compilação que uma yield instrução (de qualquer forma) apareça dentro de uma função anónima.
  • É um erro em tempo de compilação se uma yield instrução (de qualquer forma) aparecer na finally cláusula de uma try instrução.
  • É um erro em tempo de compilação uma instrução yield return aparecer em qualquer lugar numa instrução try que contenha quaisquer catch_clauses.

Exemplo: O exemplo a seguir mostra alguns usos válidos e inválidos de yield instruçõ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 final

Uma conversão implícita (§10.2) deve existir do tipo de expressão na yield return declaração para o tipo de rendimento (§15.15.4) do iterador.

Uma yield return instrução é executada da seguinte forma:

  • A expressão dada na instrução é avaliada, implicitamente convertida para o tipo de rendimento e atribuída à Current propriedade do objeto enumerador.
  • A execução do bloco iterador é suspensa. Se a yield return instrução estiver dentro de um ou mais try blocos, os blocos associados finallynão serão executados neste momento.
  • O MoveNext método do objeto enumerador retorna true ao seu 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 iterador de onde ele foi suspenso pela última vez.

Uma yield break instrução é executada da seguinte forma:

  • Se a yield break instrução estiver delimitada por um ou mais blocos try com blocos finally associados, o controle será inicialmente transferido para o bloco finally da instrução try mais interna. Quando e se o controlo atingir o ponto final de um finally bloco, o controlo será transferido para o bloco finally da próxima instrução envolvente try. Este processo é repetido até que os finally blocos de todas as instruções anexas try tenham sido executados.
  • O controle é retornado ao chamador do bloco iterador. Este é quer o método MoveNext quer o método Dispose 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.