Partilhar via


9 Variáveis

9.1 Generalidades

As variáveis representam locais de armazenamento. Cada variável tem um tipo que determina quais valores podem ser armazenados na variável. C# é uma linguagem segura para tipos, e o compilador C# garante que os valores armazenados em variáveis sejam sempre do tipo apropriado. O valor de uma variável pode ser alterado através da atribuição ou através do uso dos ++ operadores e -- .

Uma variável deve ser definitivamente atribuída (§9.4) antes de se poder obter o seu valor.

Conforme descrito nas subcláusulas a seguir, as variáveis são inicialmente atribuídas ou inicialmente não atribuídas. Uma variável inicialmente atribuída tem um valor inicial bem definido e é sempre considerada definitivamente atribuída. Uma variável inicialmente não atribuída não tem valor inicial. Para que uma variável inicialmente não atribuída seja considerada definitivamente atribuída num determinado local, deve ocorrer uma atribuição à variável em todos os caminhos de execução possíveis que conduzam a esse local.

9.2 Categorias de variáveis

9.2.1 Generalidades

O C# define oito categorias de variáveis: variáveis estáticas, variáveis de instância, elementos de matriz, parâmetros de valor, parâmetros de entrada, parâmetros de referência, parâmetros de saída e variáveis locais. As subcláusulas a seguir descrevem cada uma dessas categorias.

Exemplo: No código a seguir

class A
{
    public static int x;
    int y;

    void F(int[] v, int a, ref int b, out int c, in int d)
    {
        int i = 1;
        c = a + b++ + d;
    }
}

x é uma variável estática, y é uma variável de instância, v[0] é um elemento de matriz, a é um parâmetro de valor, b é um parâmetro de referência, c é um parâmetro de saída, d é um parâmetro de entrada e i é uma variável local. Exemplo final

9.2.2 Variáveis estáticas

Um campo declarado com o static modificador é uma variável estática. Uma variável estática surge antes da execução do static construtor (§15.12) para o tipo que a contém, e deixa de existir quando o domínio de aplicação associado deixa de existir.

O valor inicial de uma variável estática é o valor padrão (§9.3) do tipo da variável.

Para fins de verificação de atribuição definitiva, uma variável estática é considerada inicialmente atribuída.

9.2.3 Variáveis de instância

9.2.3.1 Generalidades

Um campo declarado sem o static modificador é uma variável de instância.

9.2.3.2 Variáveis de instância em classes

Uma variável de instância de uma classe surge quando uma nova instância dessa classe é criada e deixa de existir quando não há referências a essa instância e o finalizador da instância (se houver) foi executado.

O valor inicial de uma variável de instância de uma classe é o valor padrão (§9.3) do tipo da variável.

Para fins de verificação de atribuição definida, uma variável de instância de uma classe é considerada inicialmente atribuída.

9.2.3.3 Variáveis de instância em structs

Uma variável de instância de uma struct tem exatamente o mesmo tempo de vida que a variável struct à qual pertence. Em outras palavras, quando uma variável de um tipo struct passa a existir ou deixa de existir, o mesmo acontece com as variáveis de instância do struct.

O estado de atribuição inicial de uma variável de instância de uma struct é o mesmo que o da variável que a contém struct . Em outras palavras, quando uma variável struct é considerada inicialmente atribuída, assim também são suas variáveis de instância, e quando uma variável struct é considerada inicialmente não atribuída, suas variáveis de instância também não são atribuídas.

9.2.4 Elementos da matriz

Os elementos de uma matriz passam a existir quando uma instância de matriz é criada e deixam de existir quando não há referências a essa instância de matriz.

O valor inicial de cada um dos elementos de uma matriz é o valor padrão (§9.3) do tipo dos elementos da matriz.

Para fins de verificação de atribuição definitiva, um elemento de matriz é considerado inicialmente atribuído.

9.2.5 Parâmetros de valor

Um parâmetro value surge após a invocação do membro da função (método, construtor de instância, acessador ou operador) ou função anônima à qual o parâmetro pertence, e é inicializado com o valor do argumento dado na invocação. Um parâmetro de valor normalmente deixa de existir quando a execução do corpo da função é concluída. No entanto, se o parâmetro value for capturado por uma função anônima (§12.19.6.2), sua vida útil se estenderá pelo menos até que o delegado ou a árvore de expressão criada a partir dessa função anônima seja elegível para coleta de lixo.

Para fins de verificação de atribuição definitiva, um parâmetro de valor é considerado inicialmente atribuído.

Os parâmetros de valor são discutidos mais detalhadamente no §15.6.2.2.

9.2.6 Parâmetros de referência

Um parâmetro de referência é uma variável de referência (§9.7) que surge após a invocação do membro da função, delegar, função anónima ou função local e o seu referente é inicializado para a variável dada como argumento nessa invocação. Um parâmetro de referência deixa de existir quando a execução do corpo da função é concluída. Ao contrário dos parâmetros de valor, não deve ser capturado um parâmetro de referência (ponto 9.7.2.9).

As seguintes regras de atribuição definida aplicam-se aos parâmetros de referência.

Nota: As regras para os parâmetros de saída são diferentes e estão descritas em (§9.2.7). Nota final

  • Uma variável deve ser definitivamente atribuída (§9.4) antes de poder ser passada como parâmetro de referência numa invocação de membro de função ou delegado.
  • Dentro de um membro de função ou função anônima, um parâmetro de referência é considerado inicialmente atribuído.

Os parâmetros de referência são analisados mais pormenorizadamente no ponto 15.6.2.3.3.

9.2.7 Parâmetros de saída

Um parâmetro de saída é uma variável de referência (§9.7) que surge após a invocação do membro da função, delegar, função anônima ou função local e seu referente é inicializado para a variável dada como o argumento nessa invocação. Um parâmetro de saída deixa de existir quando a execução do corpo da função é concluída. Ao contrário dos parâmetros de valor, um parâmetro de saída não deve ser capturado (§9.7.2.9).

As seguintes regras de atribuição definida aplicam-se aos parâmetros de saída.

Nota: As regras para os parâmetros de referência são diferentes e estão descritas em (§9.2.6). Nota final

  • Uma variável não precisa ser definitivamente atribuída antes de poder ser passada como um parâmetro de saída em uma chamada de membro de função ou delegado.
  • Após a conclusão normal de uma chamada de membro de função ou delegado, cada variável que foi passada como um parâmetro de saída é considerada atribuída nesse caminho de execução.
  • Dentro de um membro da função ou função anônima, um parâmetro de saída é considerado inicialmente não atribuído.
  • Cada parâmetro de saída de um membro da função, função anónima ou função local deve ser definitivamente atribuído (§9.4) antes de o membro da função, função anónima ou função local regressar normalmente.

Os parâmetros de saída são discutidos mais detalhadamente no §15.6.2.3.4.

9.2.8 Parâmetros de entrada

Um parâmetro de entrada é uma variável de referência (§9.7) que surge após a invocação do membro da função, delegar, função anônima ou função local e seu referente é inicializado para o variable_reference dado como o argumento nessa invocação. Um parâmetro de entrada deixa de existir quando a execução do corpo da função é concluída. Ao contrário dos parâmetros de valor, um parâmetro de entrada não deve ser capturado (§9.7.2.9).

As seguintes regras de atribuição definidas aplicam-se aos parâmetros de entrada.

  • Uma variável deve ser definitivamente atribuída (§9.4) antes de poder ser passada como parâmetro de entrada numa chamada de membro de função ou delegado.
  • Dentro de um membro de função, função anônima ou função local, um parâmetro de entrada é considerado inicialmente atribuído.

Os parâmetros de entrada são discutidos mais detalhadamente no §15.6.2.3.2.

9.2.9 Variáveis locais

9.2.9.1 Generalidades

Uma variável local é declarada por um local_variable_declaration, declaration_expression, foreach_statement ou specific_catch_clause de um try_statement. Uma variável local também pode ser declarada por certos tipos de padrões(§11). Para um foreach_statement, a variável local é uma variável de iteração (§13.9.5). Para um specific_catch_clause, a variável local é uma variável de exceção (§13.11). Uma variável local declarada por um foreach_statement ou specific_catch_clause é considerada inicialmente atribuída.

Um local_variable_declaration pode ocorrer em um bloco, um for_statement, um switch_block ou um using_statement. Uma declaration_expression pode ocorrer como um out argument_value e como um tuple_element que é o alvo de uma atribuição de desconstrução (§12.21.2).

O tempo de vida de uma variável local é a parte da execução do programa durante a qual o armazenamento é garantido para ser reservado para ela. Esta vida útil estende-se desde a entrada no âmbito a que está associada, pelo menos até que a execução desse âmbito termine de alguma forma. (Inserir um bloco fechado, chamar um método ou produzir um valor de um bloco iterador suspende, mas não encerra, a execução do escopo atual.) Se a variável local for capturada por uma função anônima (§12.19.6.2), sua vida útil se estende pelo menos até que o delegado ou a árvore de expressão criada a partir da função anônima, juntamente com quaisquer outros objetos que venham a fazer referência à variável capturada, sejam elegíveis para coleta de lixo. Se o escopo pai for inserido recursiva ou iterativamente, uma nova instância da variável local será criada a cada vez, e seu inicializador, se houver, será avaliado a cada vez.

Nota: Uma variável local é instanciada cada vez que seu escopo é inserido. Esse comportamento é visível para o código do usuário que contém métodos anônimos. Nota final

Nota: O tempo de vida de uma variável de iteração (§13.9.5) declarado por um foreach_statement é uma única iteração dessa instrução. Cada iteração cria uma nova variável. Nota final

Nota: O tempo de vida real de uma variável local depende da implementação. Por exemplo, um compilador pode determinar estaticamente que uma variável local em um bloco é usada apenas para uma pequena parte desse bloco. Usando essa análise, um compilador pode gerar código que resulta no armazenamento da variável ter uma vida útil mais curta do que o bloco que a contém.

O armazenamento referido por uma variável de referência local é recuperado independentemente do tempo de vida dessa variável de referência local (§7.9).

Nota final

Uma variável local introduzida por um local_variable_declaration ou declaration_expression não é inicializada automaticamente e, portanto, não tem valor padrão. Tal variável local é considerada inicialmente não atribuída.

Nota: Um local_variable_declaration que inclui um inicializador ainda não foi atribuído inicialmente. A execução da declaração comporta-se exatamente como uma atribuição à variável (§9.4.4.5). Usando uma variável antes de seu inicializador ter sido executado; por exemplo, dentro da própria expressão do inicializador ou usando um goto_statement que ignora o inicializador; é um erro em tempo de compilação:

goto L;

int x = 1; // never executed

L: x += 1; // error: x not definitely assigned

Dentro do escopo de uma variável local, é um erro em tempo de compilação referir-se a essa variável local em uma posição textual que precede seu declarador.

Nota final

9.2.9.2 Devoluções

Um descarte é uma variável local que não tem nome. Um descarte é introduzido por uma expressão de declaração (§12.17T _

Nota: _ é um identificador válido em muitas formas de declarações. Nota final

Como um descarte não tem nome, a única referência à variável que ele representa é a expressão que o introduz.

Nota: Um descarte pode, no entanto, ser passado como um argumento de saída, permitindo que o parâmetro de saída correspondente denote seu local de armazenamento associado. Nota final

Um descarte não é atribuído inicialmente, por isso é sempre um erro acessar seu valor.

Exemplo:

_ = "Hello".Length;
(int, int, int) M(out int i1, out int i2, out int i3) { ... }
(int _, var _, _) = M(out int _, out var _, out _);

O exemplo pressupõe que não há nenhuma declaração do nome _ no escopo.

A atribuição para _ mostra um padrão simples para ignorar o resultado de uma expressão. A chamada de mostra as diferentes formas de descarte disponíveis em tuplas e como parâmetros de M saída.

Exemplo final

9.3 Valores padrão

As seguintes categorias de variáveis são inicializadas automaticamente para seus valores padrão:

  • Variáveis estáticas.
  • Variáveis de instância de instâncias de classe.
  • Elementos de matriz.

O valor padrão de uma variável depende do tipo da variável e é determinado da seguinte forma:

  • Para uma variável de um value_type, o valor padrão é o mesmo que o valor calculado pelo construtor padrão do value_type (§8.3.3).
  • Para uma variável de um reference_type, o valor padrão é null.

Nota: A inicialização para valores padrão geralmente é feita fazendo com que o gerenciador de memória ou o coletor de lixo inicialize a memória para todos os bits zero antes de ser alocada para uso. Por esse motivo, é conveniente usar all-bits-zero para representar a referência nula. Nota final

9.4 Atribuição definitiva

9.4.1 Generalidades

Em um determinado local no código executável de um membro da função ou de uma função anônima, diz-se que uma variável é definitivamente atribuída se um compilador puder provar, por uma análise de fluxo estático particular (§9.4.4), que a variável foi inicializada automaticamente ou foi alvo de pelo menos uma atribuição.

Nota: Declaradas informalmente, as regras de atribuição definitiva são:

  • Uma variável inicialmente atribuída (§9.4.2) é sempre considerada definitivamente atribuída.
  • Uma variável inicialmente não atribuída (§9.4.3) é considerada definitivamente atribuída num determinado local se todos os caminhos de execução possíveis que conduzem a esse local contiverem pelo menos um dos seguintes elementos:
    • Uma atribuição simples (§12.21.2) em que a variável é o operando esquerdo.
    • Uma expressão de invocação (§12.8.10) ou expressão de criação de objeto (§12.8.17.2) que passa a variável como um parâmetro de saída.
    • Para uma variável local, uma declaração de variável local para a variável (§13.6.2) que inclui um inicializador de variável.

A especificação formal subjacente às regras informais acima referidas é descrita nos §9.4.2, §9.4.3 e §9.4.4.

Nota final

Os estados de atribuição definitiva das variáveis de instância de uma variável struct_type são rastreados individual e coletivamente. Além das regras descritas nos §9.4.2, §9.4.3 e §9.4.4, aplicam-se as seguintes regras às variáveis struct_type e às suas variáveis de instância:

  • Uma variável de instância é considerada definitivamente atribuída se sua variável contendo struct_type for considerada definitivamente atribuída.
  • Uma variável struct_type é considerada definitivamente atribuída se cada uma de suas variáveis de instância for considerada definitivamente atribuída.

A atribuição definitiva é um requisito nos seguintes contextos:

  • Uma variável deve ser definitivamente atribuída em cada local onde o seu valor é obtido.

    Nota: Isso garante que valores indefinidos nunca ocorram. Nota final

    A ocorrência de uma variável em uma expressão é considerada para obter o valor da variável, exceto quando

    • a variável é o operando esquerdo de uma atribuição simples,
    • a variável é passada como um parâmetro de saída, ou
    • A variável é uma variável struct_type e ocorre como o operando esquerdo de um acesso de membro.
  • Uma variável deve ser definitivamente atribuída em cada local onde é passada como parâmetro de referência.

    Nota: Isso garante que o membro da função que está sendo invocado possa considerar o parâmetro de referência inicialmente atribuído. Nota final

  • Uma variável deve ser definitivamente atribuída em cada local onde é passada como parâmetro de entrada.

    Nota: Isso garante que o membro da função que está sendo invocado possa considerar o parâmetro de entrada inicialmente atribuído. Nota final

  • Todos os parâmetros de saída de um membro da função devem ser definitivamente atribuídos em cada local onde o membro da função retorna (através de uma instrução de retorno ou através da execução que chega ao final do corpo do membro da função).

    Nota: Isso garante que os membros da função não retornem valores indefinidos nos parâmetros de saída, permitindo assim que um compilador considere uma invocação de membro da função que toma uma variável como um parâmetro de saída equivalente a uma atribuição à variável. Nota final

  • A this variável de um construtor de instância struct_type deve ser definitivamente atribuída em cada local onde esse construtor de instância retorna.

9.4.2 Variáveis inicialmente atribuídas

As seguintes categorias de variáveis são classificadas como inicialmente atribuídas:

  • Variáveis estáticas.
  • Variáveis de instância de instâncias de classe.
  • Variáveis de instância de variáveis struct inicialmente atribuídas.
  • Elementos de matriz.
  • Parâmetros de valor.
  • Parâmetros de referência.
  • Parâmetros de entrada.
  • Variáveis declaradas em uma catch cláusula ou instrução foreach .

9.4.3 Variáveis inicialmente não atribuídas

As seguintes categorias de variáveis são classificadas como inicialmente não atribuídas:

  • Variáveis de instância de variáveis struct inicialmente não atribuídas.
  • Parâmetros de saída, incluindo a this variável de construtores de instância struct sem um inicializador de construtor.
  • Variáveis locais, exceto aquelas declaradas em uma catch cláusula ou instrução foreach .

9.4.4 Regras precisas para determinar a atribuição definitiva

9.4.4.1 Generalidades

Para determinar que cada variável usada é definitivamente atribuída, um compilador deve usar um processo equivalente ao descrito nesta subcláusula.

O corpo de um membro da função pode declarar uma ou mais variáveis inicialmente não atribuídas. Para cada variável inicialmente não atribuída v, um compilador deve determinar um de estado de atribuição definida para v em cada um dos seguintes pontos no membro da função:

  • No início de cada declaração
  • No ponto final (§13.2) de cada declaração
  • Em cada arco que transfere o controle para outra instrução ou para o ponto final de uma instrução
  • No início de cada expressão
  • No final de cada expressão

O estado de atribuição definitiva de v pode ser:

  • Definitivamente atribuído. Isso indica que, em todos os fluxos de controle possíveis até este ponto, v recebeu um valor.
  • Não definitivamente atribuído. Para o estado de uma variável no final de uma expressão do tipo bool, o estado de uma variável que não é definitivamente atribuída pode (mas não necessariamente) cair em um dos seguintes subestados:
    • Definitivamente atribuído após a expressão verdadeira. Este estado indica que v é definitivamente atribuído se a expressão booleana avaliada como verdadeira, mas não é necessariamente atribuído se a expressão booleana avaliada como falsa.
    • Definitivamente atribuído após falsa expressão. Este estado indica que v é definitivamente atribuído se a expressão booleana avaliada como falsa, mas não é necessariamente atribuído se a expressão booleana avaliada como verdadeira.

As regras a seguir regem como o estado de uma variável v é determinado em cada local.

9.4.4.2 Regras gerais aplicáveis às declarações

  • v não é definitivamente atribuído no início de um órgão membro da função.
  • O estado de atribuição definitiva de v no início de qualquer outra instrução é determinado verificando o estado de atribuição definida de v em todas as transferências de fluxo de controle que visam o início dessa instrução. Se (e somente se) v é definitivamente atribuído em todas essas transferências de fluxo de controle, então v é definitivamente atribuído no início da instrução. O conjunto de possíveis transferências de fluxo de controlo é determinado da mesma forma que para verificar a acessibilidade da declaração (§13.2).
  • O estado de atribuição definida de v no ponto final de uma instrução , block, checked, unchecked, if, whiledoforforeachlockou using é determinado verificando o estado de atribuição definida de vswitchcontrole que visam o ponto final dessa instrução. Se v é definitivamente atribuído em todas essas transferências de fluxo de controle, então v é definitivamente atribuído no ponto final da instrução. Caso contrário, v não é definitivamente atribuído no ponto final da instrução. O conjunto de possíveis transferências de fluxo de controlo é determinado da mesma forma que para verificar a acessibilidade da declaração (§13.2).

Nota: Como não há caminhos de controle para uma instrução inacessível, v é definitivamente atribuído no início de qualquer instrução inacessível. Nota final

9.4.4.3 Instruções em bloco, verificadas e não verificadas

O estado de atribuição definitiva de v na transferência de controle para a primeira instrução da lista de instruções no bloco (ou para o ponto final do bloco, se a lista de instruções estiver vazia) é o mesmo que a instrução de atribuição definida de v antes do bloco, checkedou unchecked instrução.

9.4.4.4 Enunciados de expressão

Para uma instrução de expressão stmt que consiste na expressão expr:

  • v tem o mesmo estado de atribuição definida no início da expr como no início da stmt.
  • Se v é definitivamente atribuído no final do stmt, ele é definitivamente atribuído no ponto final do stmt, caso contrário, não é definitivamente atribuído no ponto final do stmt.

9.4.4.5 Declarações

  • Se stmt é uma instrução de declaração sem inicializadores, então v tem o mesmo estado de atribuição definida no ponto final de stmt como no início de stmt.
  • Se stmt for uma instrução de declaração com inicializadores, o estado de atribuição definida para v será determinado como se stmt fosse uma lista de instruções, com uma instrução de atribuição para cada declaração com um inicializador (na ordem da declaração).

9.4.4.6 Se as declarações

Para uma declaração stmt do formulário:

if ( «expr» ) «then_stmt» else «else_stmt»
  • v tem o mesmo estado de atribuição definida no início da expr como no início da stmt.
  • Se v é definitivamente atribuído no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para then_stmt e para else_stmtou para o ponto final de stmt se não houver outra cláusula.
  • Se v tem o estado "definitivamente atribuído após a expressão verdadeira" no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para then_stmt, e não definitivamente atribuído na transferência de fluxo de controle para else_stmtou para o ponto final de stmt se não houver outra cláusula.
  • Se v tem o estado "definitivamente atribuído após expressão falsa" no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para else_stmt, e não definitivamente atribuído na transferência de fluxo de controle para then_stmt. Ele é definitivamente atribuído no ponto final do stmt se e somente se for definitivamente atribuído no ponto final de then_stmt.
  • Caso contrário, considera-se que v não é definitivamente atribuído na transferência de fluxo de controle para o then_stmt ou else_stmt, ou para o ponto final de stmt se não houver outra cláusula.

9.4.4.7 Instruções de comutação

Para uma switch instrução stmt com uma expressão de controle expr:

O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de v no início de stmt.

O estado de atribuição definitiva de v no início da cláusula de guarda de um caso é

  • Se v é uma variável padrão declarada no switch_label: "definitivamente atribuído".
  • Se o rótulo do interruptor que contém essa cláusula de proteção (§13.8.3) não estiver acessível: "definitivamente atribuído".
  • Caso contrário, o estado de v é o mesmo que o estado de v após expr.

Exemplo: A segunda regra elimina a necessidade de um compilador emitir um erro se uma variável não atribuída for acessada em código inacessível. O estado de bcase 2 when binterruptor inacessível.

bool b;
switch (1) 
{
    case 2 when b: // b is definitely assigned here.
    break;
}

Exemplo final

O estado de atribuição definitiva de v na transferência do fluxo de controle para uma lista de instruções de bloco de switch acessível é

  • Se a transferência de controle foi devido a uma instrução 'goto case' ou 'goto default', então o estado de v é o mesmo que o estado no início dessa instrução 'goto'.
  • Se a transferência de controle foi devido ao default rótulo do switch, então o estado de v é o mesmo que o estado de v após expr.
  • Se a transferência de controle foi devido a um rótulo de switch inacessível, então o estado de v é "definitivamente atribuído".
  • Se a transferência de controle foi devido a uma etiqueta de interruptor acessível com uma cláusula de proteção, então o estado de v é o mesmo que o estado de v após a cláusula de proteção.
  • Se a transferência de controle foi devido a uma etiqueta de interruptor acessível sem uma cláusula de proteção, então o estado de v é
    • Se v é uma variável padrão declarada no switch_label: "definitivamente atribuído".
    • Caso contrário, o estado de v é o mesmo que o stat de v após expr.

Uma consequência dessas regras é que uma variável de padrão declarada em um switch_label será "não definitivamente atribuída" nas instruções de sua seção de switch se não for o único rótulo de switch acessível em sua seção.

Exemplo:

public static double ComputeArea(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            // none of s, c, t, or r is definitely assigned
            return 0;
        case Square s:
            // s is definitely assigned
            return s.Side * s.Side;
        case Circle c:
            // c is definitely assigned
            return c.Radius * c.Radius * Math.PI;
           …
    }
}

Exemplo final

9.4.4.8 Enquanto declarações

Para uma declaração stmt do formulário:

while ( «expr» ) «while_body»
  • v tem o mesmo estado de atribuição definida no início da expr como no início da stmt.
  • Se v é definitivamente atribuído no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para while_body e para o ponto final de stmt.
  • Se v tem o estado "definitivamente atribuído após a expressão verdadeira" no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para while_body, mas não definitivamente atribuído no ponto final do stmt.
  • Se v tem o estado "definitivamente atribuído após falsa expressão" no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para o ponto final de stmt, mas não definitivamente atribuído na transferência de fluxo de controle para while_body.

9.4.4.9 Fazer declarações

Para uma declaração stmt do formulário:

do «do_body» while ( «expr» ) ;
  • v tem o mesmo estado de atribuição definida na transferência de fluxo de controle do início do STMTT para do_body como no início do STMT.
  • v tem o mesmo estado de atribuição definida no início da expr como no ponto final de do_body.
  • Se v é definitivamente atribuído no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para o ponto final de stmt.
  • Se v tem o estado "definitivamente atribuído após falsa expressão" no final da expr, então ele é definitivamente atribuído na transferência de fluxo de controle para o ponto final de stmt, mas não definitivamente atribuído na transferência de fluxo de controle para do_body.

9.4.4.10 Para as declarações

Para uma declaração do formulário:

for ( «for_initializer» ; «for_condition» ; «for_iterator» )
    «embedded_statement»

A verificação da atribuição definitiva é feita como se a declaração estivesse escrita:

{
    «for_initializer» ;
    while ( «for_condition» )
    {
        «embedded_statement» ;
        LLoop: «for_iterator» ;
    }
}

com continue declarações que visam a declaração sendo traduzidas for para goto declarações direcionadas ao rótulo LLoop. Se o for_condition for omitido da for declaração, a avaliação da atribuição definida prossegue como se for_condition fosse substituída por verdadeira na expansão acima.

9.4.4.11 Interromper, continuar e ir para declarações

O estado de atribuição definida de v na transferência de fluxo de controle causada por uma breakinstrução , continue, ou goto é o mesmo que o estado de atribuição definida de v no início da instrução.

9.4.4.12 Declarações de lançamento

Para uma declaração stmt do formulário:

throw «expr» ;

O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de atribuição definida de v no início de stmt.

9.4.4.13 Declarações de devolução

Para uma declaração stmt do formulário:

return «expr» ;
  • O estado de atribuição definida de v no início de expr é o mesmo que o estado de atribuição definida de v no início de stmt.
  • Se v for um parâmetro de saída, então deve ser-lhe definitivamente atribuído:
    • após expr
    • ou no final do finally bloco de um try-finallyou try-catch-finally que encerra a return declaração.

Para uma declaração stmt do formulário:

return ;
  • Se v for um parâmetro de saída, então deve ser-lhe definitivamente atribuído:
    • antes do stmt
    • ou no final do finally bloco de um try-finallyou try-catch-finally que encerra a return declaração.

9.4.4.14 Declarações de tentativa de captura

Para uma declaração stmt do formulário:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • O estado de atribuição definitiva de v no início de try_block é o mesmo que o estado de atribuição definida de v no início de stmt.
  • O estado de atribuição definitiva de v no início de catch_block_i (para qualquer i) é o mesmo que o estado de atribuição definida de v no início de stmt.
  • O estado de atribuição definitiva de v no ponto final de stmt é definitivamente atribuído se (e somente se) v é definitivamente atribuído no ponto final de try_block e a cada catch_block_i (para cada i de 1 a n).

9.4.4.15 Instruções de tentativa final

Para uma declaração stmt do formulário:

try «try_block» finally «finally_block»
  • O estado de atribuição definitiva de v no início de try_block é o mesmo que o estado de atribuição definida de v no início de stmt.
  • O estado de atribuição definitiva de v no início de finally_block é o mesmo que o estado de atribuição definida de v no início de stmt.
  • O estado de atribuição definitiva de v no ponto final de stmt é definitivamente atribuído se (e somente se) pelo menos uma das seguintes opções for verdadeira:
    • v é definitivamente atribuído no ponto final de try_block
    • v é definitivamente atribuído no ponto final de finally_block

Se uma transferência de fluxo de controle (como uma goto instrução) é feita que começa dentro de try_block e termina fora de try_block, então v também é considerado definitivamente atribuído nessa transferência de fluxo de controle se v é definitivamente atribuído no ponto final de finally_block. (Este não é um único se—se v é definitivamente atribuído por outro motivo nesta transferência de fluxo de controle, então ele ainda é considerado definitivamente atribuído.)

9.4.4.16 Declarações Try-catch-finally

Para uma declaração do formulário:

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
finally «finally_block»

A análise de atribuição definitiva é feita como se a declaração fosse uma try-finally declaração anexando uma try-catch declaração:

try
{
    try «try_block»
    catch ( ... ) «catch_block_1»
    ...
    catch ( ... ) «catch_block_n»
}
finally «finally_block»

Exemplo: O exemplo a seguir demonstra como os diferentes blocos de uma try instrução (§13.11) afetam a atribuição definida.

class A
{
    static void F()
    {
        int i, j;
        try
        {
            goto LABEL;
            // neither i nor j definitely assigned
            i = 1;
            // i definitely assigned
        }
        catch
        {
            // neither i nor j definitely assigned
            i = 3;
            // i definitely assigned
        }
        finally
        {
            // neither i nor j definitely assigned
            j = 5;
            // j definitely assigned
        }
        // i and j definitely assigned
        LABEL: ;
        // j definitely assigned
    }
}

Exemplo final

9.4.4.17 Para cada declaração

Para uma declaração stmt do formulário:

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definitiva de v na transferência do fluxo de controle para embedded_statement ou para o ponto final de stmt é o mesmo que o estado de v no final de expr.

9.4.4.18 Utilização das instruções

Para uma declaração stmt do formulário:

using ( «resource_acquisition» ) «embedded_statement»
  • O estado de atribuição definitiva de v no início de resource_acquisition é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definitiva de v na transferência do fluxo de controle para embedded_statement é o mesmo que o estado de v no final de resource_acquisition.

9.4.4.19 Instruções de bloqueio

Para uma declaração stmt do formulário:

lock ( «expr» ) «embedded_statement»
  • O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definitiva de v na transferência de fluxo de controle para embedded_statement é o mesmo que o estado de v no final de expr.

9.4.4.20 Declarações de rendimento

Para uma declaração stmt do formulário:

yield return «expr» ;
  • O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definitiva de v no final de stmt é o mesmo que o estado de v no final de expr.

Uma yield break instrução não tem efeito sobre o estado de atribuição definida.

9.4.4.21 Regras gerais para expressões constantes

O seguinte se aplica a qualquer expressão constante e tem prioridade sobre quaisquer regras das seguintes seções que possam ser aplicáveis:

Para uma expressão constante com valor true:

  • Se v é definitivamente atribuído antes da expressão, então v é definitivamente atribuído após a expressão.
  • Caso contrário, v é "definitivamente atribuído após a falsa expressão" após a expressão.

Exemplo:

int x;
if (true) {}
else
{
    Console.WriteLine(x);
}

Exemplo final

Para uma expressão constante com valor false:

  • Se v é definitivamente atribuído antes da expressão, então v é definitivamente atribuído após a expressão.
  • Caso contrário, v é "definitivamente atribuído após a expressão verdadeira" após a expressão.

Exemplo:

int x;
if (false)
{
    Console.WriteLine(x);
}

Exemplo final

Para todas as outras expressões constantes, o estado de atribuição definida de v após a expressão é o mesmo que o estado de atribuição definida de v antes da expressão.

9.4.4.22 Regras gerais para expressões simples

A seguinte regra aplica-se a estes tipos de expressões: literais (§12.8.2), nomes simples (§12.8.4), expressões de acesso de membro (§12.8.7), expressões de acesso de base não indexadas (§12.8.15), expressões typeof (§12.8.18), expressões de valor padrão (§12.8.21), expressões nameof (§12.8.23) e expressões de declaração (§12.17).

  • O estado de atribuição definitiva de v no final de tal expressão é o mesmo que o estado de atribuição definida de v no início da expressão.

9.4.4.23 Regras gerais para expressões incorporadas

As seguintes regras aplicam-se a estes tipos de expressões: expressões entre parênteses (§12.8.5), expressões de tupla (§12.8.6), expressões de acesso a elementos (§12.8.12), expressões de acesso de base com indexação (§12.8.15), expressões de incremento e decréscimo (§12.8.16, §12.9.6), expressões de coerção (§12.9.7), expressões unárias +, -, ~, * , expressões binárias +, -, *, /, %, <<, >>, <, <=, >, >=, ==, !=, is, as, &, |, ^ (§12.10, §12.11, §12.12, §12.13), expressões de atribuição compostas (§12.21.4), expressões checked e unchecked (§12.8.20), expressões de criação de arrays e delegados (§12.8.17) e expressões await (§12.9.8).

Cada uma dessas expressões tem uma ou mais subexpressões que são avaliadas incondicionalmente em uma ordem fixa.

Exemplo: O operador binário % avalia o lado esquerdo do operador e, em seguida, o lado direito. Uma operação de indexação avalia a expressão indexada e, em seguida, avalia cada uma das expressões de índice, em ordem da esquerda para a direita. Exemplo final

Para uma expressão expr, que tem subexpressões expr₁, expr₂, ..., exprₓ, avaliadas nessa ordem:

  • O estado de atribuição definida de v no início de expr₁ é o mesmo que o estado de atribuição definida no início de expr.
  • O estado de atribuição definitiva de v no início de expri (i maior que um) é o mesmo que o estado de atribuição definida no final de expri₋₁.
  • O estado de atribuição definitiva de v no final de expr é o mesmo que o estado de atribuição definida no final de exprₓ.

9.4.4.24 Expressões de invocação e expressões de criação de objetos

Se o método a ser invocado for um método parcial que não tenha nenhuma declaração de método parcial de implementação, ou seja um método condicional para o qual a chamada é omitida (§22.5.3.2), então o estado de atribuição definida de v após a invocação é o mesmo que o estado de atribuição definida de v antes da invocação. Caso contrário, aplicam-se as seguintes regras:

Para uma expressão de invocação expr do formulário:

«primary_expression» ( «arg₁», «arg₂», … , «argₓ» )

ou uma expressão de criação de objeto expr do formulário:

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • Para uma expressão de invocação, o estado de atribuição definido de v antes de primary_expression é o mesmo que o estado de v antes de expr.
  • Para uma expressão de invocação, o estado de atribuição definido de v antes de arg₁ é o mesmo que o estado de v após primary_expression.
  • Para uma expressão de criação de objeto, o estado de atribuição definido de v antes de arg₁ é o mesmo que o estado de v antes de expr.
  • Para cada argumento argi, o estado de atribuição definido de v após argi é determinado pelas regras normais de expressão, ignorando qualquer in, outou ref modificadores.
  • Para cada argumento argi para qualquer i maior que um, o estado de atribuição definido de v antes de argi é o mesmo que o estado de v depois de argi₋₁.
  • Se a variável v é passada como um argumento (ou seja, um argumento da forma "out out") em qualquer um dos argumentos, então o estado de v após expr é definitivamente atribuído. Caso contrário, o estado de v após expr é o mesmo que o estado de v após argₓ.
  • Para inicializadores de matriz (§12.8.17.5), inicializadores de objeto (§12.8.17.3), inicializadores de coleção (§12.8.17.4) e inicializadores de objetos anônimos (§12.8.17.7), o estado de atribuição definida é determinado pela expansão em que essas construções são definidas.

9.4.4.25 Expressões de atribuição simples

Deixe que o conjunto de metas de atribuição em uma expressão e seja definido da seguinte forma:

  • Se e é uma expressão de tupla, então os alvos de atribuição em e são a união dos alvos de atribuição dos elementos de e.
  • Caso contrário, os alvos de atribuição em e são e.

Para uma expressão expr do formulário:

«expr_lhs» = «expr_rhs»
  • O estado de atribuição definitiva de v antes de expr_lhs é o mesmo que o estado de atribuição definida de v antes de expr.
  • O estado de atribuição definida de v antes de expr_rhs é o mesmo que o estado de atribuição definida de v após expr_lhs.
  • Se v é um destino de atribuição de expr_lhs, então o estado de atribuição definida de v após expr é definitivamente atribuído. Caso contrário, se a atribuição ocorrer dentro do construtor de instância de um tipo struct e v for o campo de suporte oculto de uma propriedade implementada automaticamente P na instância que está sendo construída, e um acesso à propriedade designando P for um destino de atribuição de expr_lhs, então o estado de atribuição definida de v após expr será definitivamente atribuído. Caso contrário, o estado de atribuição definida de v após expr é o mesmo que o estado de atribuição definida de v após expr_rhs.

Exemplo: No código a seguir

class A
{
    static void F(int[] arr)
    {
        int x;
        arr[x = 1] = x; // ok
    }
}

A variável x é considerada definitivamente atribuída depois arr[x = 1] de ser avaliada como o lado esquerdo da segunda tarefa simples.

Exemplo final

9.4.4.26 && expressões

Para uma expressão expr do formulário:

«expr_first» && «expr_second»
  • O estado de atribuição definitiva de v antes de expr_first é o mesmo que o estado de atribuição definida de v antes de expr.
  • O estado de atribuição definitiva de v antes de expr_second é definitivamente atribuído se e somente se o estado de v após expr_first for definitivamente atribuído ou "definitivamente atribuído após a expressão verdadeira". Caso contrário, não é definitivamente atribuído.
  • O estado de atribuição definitiva de v após expr é determinado por:
    • Se o estado de v após expr_first é definitivamente atribuído, então o estado de v após expr é definitivamente atribuído.
    • Caso contrário, se o estado de v após expr_second é definitivamente atribuído, e o estado de v após expr_first é "definitivamente atribuído após expressão falsa", então o estado de v após expr é definitivamente atribuído.
    • Caso contrário, se o estado de v após expr_second é definitivamente atribuído ou "definitivamente atribuído após a expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após a expressão verdadeira".
    • Caso contrário, se o estado de v após expr_first é "definitivamente atribuído após expressão falsa", e o estado de v após expr_second é "definitivamente atribuído após expressão falsa", então o estado de v após expr é "definitivamente atribuído após expressão falsa".
    • Caso contrário, o estado de v após expr não é definitivamente atribuído.

Exemplo: No código a seguir

class A
{
    static void F(int x, int y)
    {
        int i;
        if (x >= 0 && (i = y) >= 0)
        {
            // i definitely assigned
        }
        else
        {
            // i not definitely assigned
        }
        // i not definitely assigned
    }
}

A variável i é considerada definitivamente atribuída em uma das instruções incorporadas de uma if instrução, mas não na outra. if Na instrução no método F, a variável i é definitivamente atribuída na primeira instrução incorporada porque a execução da expressão (i = y) sempre precede a execução dessa instrução incorporada. Em contraste, a variável i não é definitivamente atribuída na segunda instrução incorporada, uma vez que x >= 0 pode ter testado false, resultando na não atribuição da variável i.

Exemplo final

9.4.4.27 || expressões

Para uma expressão expr do formulário:

«expr_first» || «expr_second»
  • O estado de atribuição definitiva de v antes de expr_first é o mesmo que o estado de atribuição definida de v antes de expr.
  • O estado de atribuição definitiva de v antes de expr_second é definitivamente atribuído se e somente se o estado de v após expr_first for definitivamente atribuído ou "definitivamente atribuído após a expressão verdadeira". Caso contrário, não é definitivamente atribuído.
  • A declaração de atribuição definitiva de v após expr é determinada por:
    • Se o estado de v após expr_first é definitivamente atribuído, então o estado de v após expr é definitivamente atribuído.
    • Caso contrário, se o estado de v após expr_second é definitivamente atribuído, e o estado de v após expr_first é "definitivamente atribuído após a expressão verdadeira", então o estado de v após expr é definitivamente atribuído.
    • Caso contrário, se o estado de v após expr_second é definitivamente atribuído ou "definitivamente atribuído após expressão falsa", então o estado de v após expr é "definitivamente atribuído após expressão falsa".
    • Caso contrário, se o estado de v após expr_first é "definitivamente atribuído após a expressão verdadeira", e o estado de v após expr_ segundo é "definitivamente atribuído após a expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após a expressão verdadeira".
    • Caso contrário, o estado de v após expr não é definitivamente atribuído.

Exemplo: No código a seguir

class A
{
    static void G(int x, int y)
    {
        int i;
        if (x >= 0 || (i = y) >= 0)
        {
            // i not definitely assigned
        }
        else
        {
            // i definitely assigned
        }
        // i not definitely assigned
    }
}

A variável i é considerada definitivamente atribuída em uma das instruções incorporadas de uma if instrução, mas não na outra. if Na instrução no método G, a variável i é definitivamente atribuída na segunda instrução incorporada porque a execução da expressão (i = y) sempre precede a execução dessa instrução incorporada. Em contraste, a variável i não é definitivamente atribuída na primeira instrução incorporada, uma vez que x >= 0 pode ter testado true, resultando na não atribuição da variável i.

Exemplo final

9.4.4.28 ! expressões

Para uma expressão expr do formulário:

! «expr_operand»
  • O estado de atribuição definitiva de v antes de expr_operand é o mesmo que o estado de atribuição definida de v antes de expr.
  • O estado de atribuição definitiva de v após expr é determinado por:
    • Se o estado de v after expr_operand é definitivamente atribuído, então o estado de v after expr é definitivamente atribuído.
    • Caso contrário, se o estado de v after expr_operand é "definitivamente atribuído após a falsa expressão", então o estado de v after expr é "definitivamente atribuído após a expressão verdadeira".
    • Caso contrário, se o estado de v after expr_operand é "definitivamente atribuído após a expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após a expressão falsa".
    • Caso contrário, o estado de v after expr não é definitivamente atribuído.

9.4.4.29 ?? expressões

Para uma expressão expr do formulário:

«expr_first» ?? «expr_second»
  • O estado de atribuição definitiva de v antes de expr_first é o mesmo que o estado de atribuição definida de v antes de expr.
  • O estado de atribuição definida de v antes de expr_second é o mesmo que o estado de atribuição definida de v após expr_first.
  • A declaração de atribuição definitiva de v após expr é determinada por:
    • Se expr_first é uma expressão constante (§12.23) com valor null, então o estado de v após expr é o mesmo que o estado de v após expr_second.
    • Caso contrário, o estado de v após expr é o mesmo que o estado de atribuição definitiva de v após expr_first.

9.4.4.30 ?: expressões

Para uma expressão expr do formulário:

«expr_cond» ? «expr_true» : «expr_false»
  • O estado de atribuição definitiva de v antes de expr_cond é o mesmo que o estado de v antes de expr.
  • O estado de atribuição definitiva de v antes de expr_true é definitivamente atribuído se o estado de v após expr_cond é definitivamente atribuído ou "definitivamente atribuído após a expressão verdadeira".
  • O estado de atribuição definitiva de v antes de expr_false é definitivamente atribuído se o estado de v após expr_cond é definitivamente atribuído ou "definitivamente atribuído após expressão falsa".
  • O estado de atribuição definitiva de v após expr é determinado por:
    • Se expr_cond é uma expressão constante (§12.23) com valor true , então o estado de v após expr é o mesmo que o estado de v após expr_true.
    • Caso contrário, se expr_cond é uma expressão constante (§12.23) com valor false , então o estado de v após expr é o mesmo que o estado de v após expr_false.
    • Caso contrário, se o estado de v após expr_true for definitivamente atribuído e o estado de v após expr_false for definitivamente atribuído, então o estado de v após expr será definitivamente atribuído.
    • Caso contrário, o estado de v após expr não é definitivamente atribuído.

9.4.4.31 Funções anónimas

Para um lambda_expression ou anonymous_method_expression expr com um corpo (bloco ou expressão):

  • O estado de atribuição definido de um parâmetro é o mesmo que para um parâmetro de um método nomeado (§9.2.6, §9.2.7, §9.2.8).
  • O estado de atribuição definido de uma variável externa v antes do corpo é o mesmo que o estado de v antes de expr. Ou seja, o estado de atribuição definido de variáveis externas é herdado do contexto da função anônima.
  • O estado de atribuição definido de uma variável externa v após expr é o mesmo que o estado de v antes de expr.

Exemplo: O exemplo

class A
{
    delegate bool Filter(int i);
    void F()
    {
        int max;
        // Error, max is not definitely assigned
        Filter f = (int n) => n < max;
        max = 5;
        DoWork(f);
    }
    void DoWork(Filter f) { ... }
}

gera um erro em tempo de compilação, uma vez que max não é definitivamente atribuído onde a função anônima é declarada.

Exemplo final

Exemplo: O exemplo

class A
{
    delegate void D();
    void F()
    {
        int n;
        D d = () => { n = 1; };
        d();
        // Error, n is not definitely assigned
        Console.WriteLine(n);
    }
}

também gera um erro em tempo de compilação, uma vez que a atribuição a n na função anônima não afeta o estado de atribuição definida de n fora da função anônima.

Exemplo final

9.4.4.32 Expressões de lançamento

Para uma expressão expr do formulário:

throw thrown_expr

  • O estado de atribuição definido de v antes de thrown_expr é o mesmo que o estado de v antes de expr.
  • O estado de atribuição definido de v após expr é "definitivamente atribuído".

9.4.4.33 Regras para variáveis em funções locais

As funções locais são analisadas no contexto de seu método pai. Há dois caminhos de fluxo de controle que são importantes para funções locais: chamadas de função e conversões delegadas.

A atribuição definida para o corpo de cada função local é definida separadamente para cada local de chamada. Em cada invocação, as variáveis capturadas pela função local são consideradas definitivamente atribuídas se tiverem sido definitivamente atribuídas no ponto de chamada. Um caminho de fluxo de controle também existe para o corpo da função local neste ponto e é considerado alcançável. Após uma chamada para a função local, as variáveis capturadas que foram definitivamente atribuídas em cada ponto de controle deixando a função (return instruções, yield instruções, await expressões) são consideradas definitivamente atribuídas após o local da chamada.

As conversões delegadas têm um caminho de fluxo de controle para o corpo da função local. As variáveis capturadas são definitivamente atribuídas para o corpo se forem definitivamente atribuídas antes da conversão. As variáveis atribuídas pela função local não são consideradas atribuídas após a conversão.

Nota: o acima implica que os corpos são re-analisados para atribuição definitiva em cada invocação de função local ou conversão de delegação. Os compiladores não são obrigados a reanalisar o corpo de uma função local em cada invocação ou conversão delegada. A execução deve produzir resultados equivalentes a essa descrição. Nota final

Exemplo: O exemplo a seguir demonstra a atribuição definida para variáveis capturadas em funções locais. Se uma função local lê uma variável capturada antes de escrevê-la, a variável capturada deve ser definitivamente atribuída antes de chamar a função local. A função F1 local lê s sem atribuí-la. É um erro se F1 é chamado antes s é definitivamente atribuído. F2 atribui i antes de lê-lo. Pode ser chamado antes i é definitivamente atribuído. Além disso, F3 pode ser chamado depois F2 porque s2 é definitivamente atribuído em F2.

void M()
{
    string s;
    int i;
    string s2;
   
    // Error: Use of unassigned local variable s:
    F1();
    // OK, F2 assigns i before reading it.
    F2();
    
    // OK, i is definitely assigned in the body of F2:
    s = i.ToString();
    
    // OK. s is now definitely assigned.
    F1();

    // OK, F3 reads s2, which is definitely assigned in F2.
    F3();

    void F1()
    {
        Console.WriteLine(s);
    }
    
    void F2()
    {
        i = 5;
        // OK. i is definitely assigned.
        Console.WriteLine(i);
        s2 = i.ToString();
    }

    void F3()
    {
        Console.WriteLine(s2);
    }
}

Exemplo final

9.4.4.34 Expressões padrão

Para uma expressão expr do formulário:

expr_operand é padrão

  • O estado de atribuição definitiva de v antes de expr_operand é o mesmo que o estado de atribuição definida de v antes de expr.
  • Se a variável 'v' é declarada no padrão, então o estado de atribuição definida de 'v' após expr é "definitivamente atribuído quando verdadeiro".
  • Caso contrário, o estado de atribuição definida de 'v' após expr é o mesmo que o estado de atribuição definida de 'v' após expr_operand.

9.5 Referências variáveis

Um variable_reference é uma expressão classificada como uma variável. Um variable_reference indica um local de armazenamento que pode ser acessado tanto para buscar o valor atual quanto para armazenar um novo valor.

variable_reference
    : expression
    ;

Nota: Em C e C++, um variable_reference é conhecido como lvalue. Nota final

9.6 Atomicidade das referências variáveis

As leituras e gravações dos seguintes tipos de dados devem ser atómicas: bool, char, byte, sbyte, short, ushort, uint, int, e floattipos de referência. Além disso, as leituras e escritas de tipos de enum com um tipo subjacente na lista anterior devem também ser atómicas. As leituras e gravações de outros tipos, incluindo long, ulong, double, e decimal, bem como os tipos definidos pelo usuário, não precisam ser atômicas. Além das funções de biblioteca projetadas para esse fim, não há garantia de leitura-modificação-gravação atômica, como no caso de incremento ou decréscimo.

9.7 Variáveis de referência e retornos

9.7.1 Generalidades

Uma variável de referência é uma variável que se refere a outra variável, chamada referente (§9.2.6). Uma variável de referência é uma variável local declarada com o ref modificador.

Uma variável de referência armazena um variable_reference (§9.5) ao seu referente e não ao valor do seu referente. Quando uma variável de referência é usada onde um valor é necessário, o valor de seu referente é retornado; Da mesma forma, quando uma variável de referência é o alvo de uma atribuição, é o referente que é atribuído. A variável a que uma variável de referência se refere, ou seja, o variable_reference armazenado para o seu referente, pode ser alterada usando uma atribuição ref (= ref).

Exemplo: O exemplo a seguir demonstra uma variável de referência local cujo referente é um elemento de uma matriz:

public class C
{
    public void M()
    {
        int[] arr = new int[10];
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        element += 5; // arr[5] has been incremented by 5
    }     
}

Exemplo final

Um retorno de referência é o variable_reference retornado de um método returns-by-ref (§15.6.1). Este variable_reference é o referente do retorno de referência.

Exemplo: O exemplo a seguir demonstra um retorno de referência cujo referente é um elemento de um campo de matriz:

public class C
{
    private int[] arr = new int[10];

    public ref readonly int M()
    {
        // element is a reference variable that refers to arr[5]
        ref int element = ref arr[5];
        return ref element; // return reference to arr[5];
    }     
}

Exemplo final

9.7.2 Ref contextos seguros

9.7.2.1 Generalidades

Todas as variáveis de referência obedecem a regras de segurança que garantem que o ref-safe-context da variável de referência não seja maior do que o ref-safe-context do seu referente.

Nota: A noção relacionada de contexto seguro é definida em (§16.4.12), juntamente com as restrições associadas. Nota final

Para qualquer variável, o ref-safe-context dessa variável é o contexto em que uma variable_reference (§9.5) a essa variável é válida. O referente de uma variável de referência deve ter um contexto ref-safe pelo menos tão amplo como o contexto ref-safe da própria variável de referência.

Nota: Um compilador determina o contexto ref-safe através de uma análise estática do texto do programa. O ref-safe-context reflete o tempo de vida de uma variável em tempo de execução. Nota final

Existem três contextos ref-safe:

  • de bloco de declaração : O contexto ref-safe de uma variable_reference a uma variável local (§9.2.9.1) é o escopo dessa variável local (§13.6.2), incluindo qualquerde declaração incorporada aninhada nesse escopo.

    Um variable_reference a uma variável local é um referente válido para uma variável de referência somente se a variável de referência for declarada dentro do contexto ref-safe-dessa variável.

  • function-member: Dentro de uma função, um variable_reference para qualquer um dos seguintes tem um ref-safe-context de function-member:

    • Parâmetros de valor (§15.6.2.2) em uma declaração de membro de função, incluindo o implícito this de funções de membro de classe;
    • O parâmetro de referência implícita (ref) (§15.6.2.3.3) this de uma função de membro struct, juntamente com os seus campos.

    Um variable_reference com ref-safe-context de function-member é um referente válido somente se a variável de referência for declarada no mesmo membro da função.

  • caller-context: Dentro de uma função, um variable_reference para qualquer um dos seguintes tem um ref-safe-context de caller-context:

    • Parâmetros de referência (§9.2.6) que não o implícito this de uma função de membro struct;
    • Campos membros e elementos desses parâmetros;
    • Campos membros de parâmetros do tipo de classe; e ainda
    • Elementos de parâmetros do tipo matriz.

Um variable_reference com ref-safe-context de caller-context pode ser o referente de um retorno de referência.

Esses valores formam uma relação de aninhamento do mais estreito (bloco de declaração) para o mais amplo (contexto do chamador). Cada bloco aninhado representa um contexto diferente.

Exemplo: O código a seguir mostra exemplos dos diferentes contextos ref-safe. As declarações mostram o ref-safe-context para um referente como sendo a expressão inicializante de uma ref variável. Os exemplos mostram o ref-safe-context para um retorno de referência:

public class C
{
    // ref safe context of arr is "caller-context". 
    // ref safe context of arr[i] is "caller-context".
    private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

    // ref safe context is "caller-context"
    public ref int M1(ref int r1)
    {
        return ref r1; // r1 is safe to ref return
    }

    // ref safe context is "function-member"
    public ref int M2(int v1)
    {
        return ref v1; // error: v1 isn't safe to ref return
    }

    public ref int M3()
    {
        int v2 = 5;

        return ref arr[v2]; // arr[v2] is safe to ref return
    }

    public void M4(int p) 
    {
        int v3 = 6;

        // context of r2 is declaration-block,
        // ref safe context of p is function-member
        ref int r2 = ref p;

        // context of r3 is declaration-block,
        // ref safe context of v3 is declaration-block
        ref int r3 = ref v3;

        // context of r4 is declaration-block,
        // ref safe context of arr[v3] is caller-context
        ref int r4 = ref arr[v3]; 
    }
}

exemplo final.

Exemplo: Para struct tipos, o parâmetro implícito this é passado como um parâmetro de referência. O contexto ref-safe-dos campos de um struct tipo como membro da função impede o retorno desses campos por retorno de referência. Esta regra impede o seguinte código:

public struct S
{
     private int n;

     // Disallowed: returning ref of a field.
     public ref int GetN() => ref n;
}

class Test
{
    public ref int M()
    {
        S s = new S();
        ref int numRef = ref s.GetN();
        return ref numRef; // reference to local variable 'numRef' returned
    }
}

exemplo final.

9.7.2.2 Variável local ref contexto seguro

Para uma variável vlocal:

  • Se v for uma variável de referência, seu ref-safe-context é o mesmo que o ref-safe-context de sua expressão inicializante.
  • Caso contrário, seu ref-safe-context é declaration-block.

9.7.2.3 Parâmetro ref contexto seguro

Para um parâmetro p:

  • Se p é uma referência ou parâmetro de entrada, seu ref-safe-context é o caller-context. Se p for um parâmetro de entrada, ele não pode ser retornado como gravável ref , mas pode ser retornado como ref readonly.
  • Se p é um parâmetro de saída, seu ref-safe-context é o caller-context.
  • Caso contrário, se p for o this parâmetro de um tipo struct, seu ref-safe-context será o function-member.
  • Caso contrário, o parâmetro é um parâmetro value e seu ref-safe-context é o function-member.

9.7.2.4 Contexto seguro de referência de campo

Para uma variável que designa uma referência a um campo, e.F:

  • Se e for de um tipo de referência, seu ref-safe-context é o caller-context.
  • Caso contrário, se e for de um tipo de valor, seu ref-safe-context será o mesmo que o ref-safe-context de e.

9.7.2.5 Operadores

O operador condicional (§12.18) c ? ref e1 : ref e2e o operador = ref e de atribuição de referência (§12.21.1) têm variáveis de referência como operandos e produzem uma variável de referência. Para esses operadores, o ref-safe-context do resultado é o contexto mais estreito entre os ref-safe-contexts de todos os ref operandos.

9.7.2.6 Invocação da função

Para uma variável c resultante de uma invocação de função ref-return, seu ref-safe-context é o mais estreito dos seguintes contextos:

  • O contexto do chamador.
  • O contexto ref-safe de todos , refoute in expressões de argumento (excluindo o recetor).
  • Para cada parâmetro de entrada, se houver uma expressão correspondente que seja uma variável e existir uma conversão de identidade entre o tipo da variável e o tipo do parâmetro, o ref-safe-context da variável, caso contrário, o contexto de fechamento mais próximo.
  • O contexto seguro (§16.4.12) de todas as expressões argumentativas (incluindo o recetor).

Exemplo: o último marcador é necessário para manipular códigos como

ref int M2()
{
    int v = 5;
    // Not valid.
    // ref safe context of "v" is block.
    // Therefore, ref safe context of the return value of M() is block.
    return ref M(ref v);
}

ref int M(ref int p)
{
    return ref p;
}

Exemplo final

Uma invocação de propriedade e uma invocação de indexador (ou getset) são tratadas como uma invocação de função do acessador subjacente pelas regras acima. Uma invocação de função local é uma invocação de função.

9.7.2.7 Valores

O ref-safe-context de um valor é o contexto de inclusão mais próximo.

Nota: Isso ocorre em uma invocação, como M(ref d.Length) onde d é do tipo dynamic. Também é consistente com os argumentos correspondentes aos parâmetros de entrada. Nota final

9.7.2.8 Invocações do construtor

Uma new expressão que invoca um construtor obedece às mesmas regras que uma invocação de método (§9.7.2.6) que é considerada para retornar o tipo que está sendo construído.

9.7.2.9 Limitações das variáveis de referência

  • Nem um parâmetro de referência, nem um parâmetro de saída, nem um parâmetro de entrada, nem um ref local, nem um parâmetro ou local de um ref struct tipo devem ser capturados pela expressão lambda ou função local.
  • Nem um parâmetro de referência, nem um parâmetro de saída, nem um parâmetro de entrada, nem um parâmetro de um ref struct tipo devem ser um argumento para um método iterador ou um async método.
  • Nem um ref local, nem um local de um ref struct tipo devem estar no contexto no ponto de uma yield return declaração ou expressão await .
  • Para uma reatribuição e1 = ref e2de ref , o contexto ref-safe de e2 deve ser pelo menos tão amplo como o contexto ref-safe de e1.
  • Para uma declaração return ref e1de retorno ref , o contexto ref-safe de deve ser o contexto do e1 chamador.