Compartilhar via


9 Variáveis

9.1 Geral

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 de tipo seguro 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 por meio da atribuição ou do uso dos ++ operadores and -- .

Uma variável deve ser definitivamente atribuída (§9.4) antes que seu valor possa ser obtido.

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 a um determinado local, uma atribuição à variável deve ocorrer em todos os caminhos de execução possíveis que levem a esse local.

9.2 Categorias de variáveis

9.2.1 Geral

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 de fim

9.2.2 Variáveis estáticas

Um campo declarado com o static modificador é uma variável estática. Uma variável estática passa a existir antes da execução do static construtor (§15.12) para seu tipo de conteúdo e deixa de existir quando o domínio do aplicativo 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 definida, uma variável estática é considerada inicialmente atribuída.

9.2.3 Variáveis de instância

9.2.3.1 Geral

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 passa a existir 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 um struct tem exatamente o mesmo tempo de vida que a variável struct à qual ela 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 um struct é o mesmo que o da variável que contém struct . Em outras palavras, quando uma variável struct é considerada inicialmente atribuída, suas variáveis de instância também são, 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 de 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 definida, um elemento de matriz é considerado inicialmente atribuído.

9.2.5 Parâmetros de valor

Um parâmetro de valor passa a existir 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 fornecido 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), seu tempo de vida se estenderá pelo menos até que o delegado ou a árvore de expressão criada a partir dessa função anônima seja qualificada para coleta de lixo.

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

Os parâmetros de valor são discutidos mais adiante em §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 passa a existir após a invocação do membro da função, delegado, função anônima ou função local e seu referente é inicializado para a variável fornecida 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, um parâmetro de referência não deve ser capturado (§9.7.2.9).

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

Observação: as regras para parâmetros de saída são diferentes e são descritas em (§9.2.7). nota final

  • Uma variável deve ser definitivamente atribuída (§9.4) antes de poder ser passada como um parâmetro de referência em uma 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 discutidos mais adiante em §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 passa a existir após a invocação do membro da função, delegado, função anônima ou função local e seu referente é inicializado para a variável fornecida como 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 regras de atribuição definida a seguir se aplicam aos parâmetros de saída.

Nota: As regras para 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 invocação de membro ou representante de função.
  • Após a conclusão normal de uma invocação 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.
  • Em um membro de 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 de função, função anônima ou função local deve ser definitivamente atribuído (§9.4) antes que o membro da função, função anônima ou função local retorne normalmente.

Os parâmetros de saída são discutidos mais adiante em §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 passa a existir após a invocação do membro da função, delegado, 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 se aplicam aos parâmetros de entrada.

  • Uma variável deve ser definitivamente atribuída (§9.4) antes de poder ser passada como um parâmetro de entrada em um membro de função ou invocação de 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 adiante em §15.6.2.3.2.

9.2.9 Variáveis locais

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ão s (§11). Por 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. Um declaration_expression pode ocorrer como um out argument_value e como um tuple_element que é o alvo de uma tarefa 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. Esse tempo de vida se estende desde a entrada no escopo ao qual está associado, pelo menos até que a execução desse escopo termine de alguma forma. (Inserir um bloco delimitado, 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), seu tempo de vida se estenderá 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 façam referência à variável capturada, sejam elegíveis para coleta de lixo. Se o escopo pai for inserido recursivamente 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

Observação: o tempo de vida de uma variável de iteração (§13.9.5) declarada 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, o compilador pode gerar código que resulta no armazenamento da variável com um tempo de vida mais curto do que o bloco que o 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. Essa variável local é considerada inicialmente não atribuída.

Observação: um local_variable_declaration que inclui um inicializador ainda não foi atribuído inicialmente. A execução da declaração se comporta exatamente como uma atribuição à variável (§9.4.4.5). Usando uma variável antes que seu inicializador seja executado; por exemplo, dentro da própria expressão do inicializador ou usando um goto_statement que ignora o inicializador; é um erro de 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 de 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.1 Devoluções

Um descarte é uma variável local que não tem nome. Um descarte é introduzido por uma expressão de declaração (§12.17) com o identificador _; e é digitado implicitamente (_ ou var _) ou explicitamente tipado (T _).

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.

Observação: No entanto, um descarte pode ser passado como um argumento de saída, permitindo que o parâmetro de saída correspondente indique seu local de armazenamento associado. nota final

Um descarte não é atribuído inicialmente, portanto, é 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 a _ mostra um padrão simples para ignorar o resultado de uma expressão. A chamada de M mostra as diferentes formas de descartes disponíveis em tuplas e como parâmetros de saída.

exemplo de fim

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

Observação: a inicialização para os valores padrão geralmente é feita fazendo com que o gerenciador de memória ou o coletor de lixo inicialize a memória para all-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 Geral

Em um determinado local no código executável de um membro de função ou de uma função anônima, uma variável é considerada definitivamente atribuída se o compilador puder provar, por uma análise de fluxo estático específica (§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 em um determinado local se todos os caminhos de execução possíveis que levam a esse local contiverem pelo menos um dos seguintes:
    • Uma atribuição simples (§12.21.2) na qual a variável é o operando esquerdo.
    • Uma expressão de invocação (§12.8.9) ou expressão de criação de objeto (§12.8.16.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 é descrita em §9.4.2, §9.4.3 e §9.4.4.

nota final

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

  • Uma variável de instância é considerada definitivamente atribuída se a variável struct_type que contém 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 seu valor é obtido.

    Observação: 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.

    Observação: isso garante que o membro da função que está sendo chamado 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.

    Observação: 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 (por meio de uma instrução de retorno ou por meio da execução que atinge o final do corpo do membro da função).

    Observação: isso garante que os membros da função não retornem valores indefinidos nos parâmetros de saída, permitindo que o compilador considere uma invocação de membro da função que usa 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 atribuídas inicialmente.
  • Elementos da 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 Geral

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

O compilador processa o corpo de cada membro da função que tem uma ou mais variáveis inicialmente não atribuídas. Para cada variável inicialmente não atribuída v, o compilador determina um 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 instruçã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 definida de v pode ser:

  • Definitivamente atribuído. Isso indica que, em todos os fluxos de controle possíveis até esse 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 verdadeira expressão. Esse estado indica que v é definitivamente atribuído se a expressão booleana for avaliada como verdadeira, mas não será necessariamente atribuída se a expressão booleana for avaliada como falsa.
    • Definitivamente atribuído após falsa expressão. Esse estado indica que v é definitivamente atribuído se a expressão booleana for avaliada como falsa, mas não será necessariamente atribuída se a expressão booleana for 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 para declarações

  • v não é definitivamente atribuído no início de um corpo de membro de função.
  • O estado de atribuição definida 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 direcionadas ao início dessa instrução. Se (e somente se) v for 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 controle é determinado da mesma forma que para verificar a acessibilidade da instrução (§13.2).
  • O estado de atribuição definida de v no ponto final de uma blockinstrução , checked, unchecked, ifwhile, forforeachdo, lockusingou switch é determinado verificando o estado de atribuição definida de v em todas as transferências de fluxo de controle direcionadas ao ponto final dessa instrução. Se v for definitivamente atribuído em todas essas transferências de fluxo de controle, então v será 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 controle é determinado da mesma forma que para verificar a acessibilidade da instrução (§13.2).

Observação: 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 de bloco, instruções verificadas e não verificadas

O estado de atribuição definida 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, checked, ou unchecked instrução.

9.4.4.4 Instruções 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 de expr e no início de stmt.
  • Se v for definitivamente atribuído no final de expr, ele é definitivamente atribuído no ponto final de stmt; caso contrário, não é definitivamente atribuído no ponto final de stmt.

9.4.4.5 Declarações de declaração

  • Se stmt for uma instrução de declaração sem inicializadores, v terá o mesmo estado de atribuição definida no ponto final de stmt e 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 Instruções If

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 de expr e no início de stmt.
  • Se v é definitivamente atribuído no final de expr, então ele é definitivamente atribuído na transferência do fluxo de controle para then_stmt e para else_stmt ou para o ponto final de stmt se não houver outra cláusula.
  • Se v tiver o estado "definitivamente atribuído após a expressão verdadeira" no final de expr, então ele é definitivamente atribuído na transferência do fluxo de controle para then_stmt, e não definitivamente atribuído na transferência do fluxo de controle para else_stmt ou para o ponto final de stmt se não houver outra cláusula.
  • Se v tiver o estado "definitivamente atribuído após expressão falsa" no final de expr, então ele é definitivamente atribuído na transferência do fluxo de controle para else_stmt, e não definitivamente atribuído na transferência do fluxo de controle para then_stmt. É definitivamente atribuído no ponto final de stmt se e somente se for definitivamente atribuído no ponto final de then_stmt.
  • Caso contrário, v é considerado não definitivamente atribuído na transferência do 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 Switch

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

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

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

  • Se v for uma variável padrão declarada no switch_label: "definitivamente atribuído".
  • Se o rótulo do switch 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 o compilador emitir um erro se uma variável não atribuída for acessada em código inacessível. O estado de b é "definitivamente atribuído" no rótulo case 2 when bdo switch inacessível .

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

exemplo de fim

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

  • Se a transferência de controle foi devido a uma instrução 'goto case' ou 'goto default', 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 um rótulo de switch alcançável com uma cláusula de guarda, então o estado de v é o mesmo que o estado de v após a cláusula de guarda.
  • Se a transferência de controle foi devido a um rótulo de switch alcançável sem uma cláusula de guarda, então o estado de v é
    • Se v for 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 alcançá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 de fim

9.4.4.8 Instruções While

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

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

9.4.4.9 Instruções Do

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 do fluxo de controle do início do STMT para o do_body que no início do STMT.
  • v tem o mesmo estado de atribuição definida no início de expr e no ponto final de do_body.
  • Se v for definitivamente atribuído no final de expr, então ele é definitivamente atribuído na transferência do fluxo de controle para o ponto final de stmt.
  • Se v tiver o estado "definitivamente atribuído após expressão falsa" no final de expr, então ele é definitivamente atribuído na transferência do fluxo de controle para o ponto final de stmt, mas não definitivamente atribuído na transferência do fluxo de controle para do_body.

9.4.4.10 Para declarações

Para uma declaração do formulário:

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

A verificação de atribuição definida é feita como se a instrução tivesse sido escrita:

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

com continue instruções direcionadas à for instrução que está sendo convertida em instruções direcionadas ao goto rótulo LLoop. Se o for_condition for omitido da instrução, a for avaliação da atribuição definida continuará como se for_condition tivesse sido substituída por true na expansão acima.

9.4.4.11 Instruções Break, continue e goto

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

9.4.4.12 Instruções throw

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

throw «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.

9.4.4.13 Declarações de retorno

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 atribuído definitivamente:
    • depois de expr
    • ou no final do finally bloco de um tryfinally-ou trycatch--finally que inclui a return instrução.

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

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

9.4.4.14 Instruções try-catch

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

try «try_block»
catch ( ... ) «catch_block_1»
...
catch ( ... ) «catch_block_n»
  • O estado de atribuição definida 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 definida 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 definida 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 try-finally

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

try «try_block» finally «finally_block»
  • O estado de atribuição definida 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 definida 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 definida de v no ponto final de stmt é definitivamente atribuído se (e somente se) pelo menos um dos seguintes for verdadeiro:
    • v é definitivamente atribuído no ponto final de try_block
    • v é definitivamente atribuído no ponto final de finally_block

Se for feita uma transferência de fluxo de controle (como uma goto instrução) 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 for definitivamente atribuído no ponto final de finally_block. (Isso não é um único se - se v for 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 Instruçõ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 definida é feita como se a instrução fosse uma try-finally instrução que inclui uma try-catch instruçã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 de fim

9.4.4.17 Instruções Foreach

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

foreach ( «type» «identifier» in «expr» ) «embedded_statement»
  • O estado de atribuição definida de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definida 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 Usando instruções

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

using ( «resource_acquisition» ) «embedded_statement»
  • O estado de atribuição definida 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 definida 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 definida de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definida de v na transferência do 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 definida de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definida 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 todas as regras das seguintes seções que podem ser aplicadas:

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 expressão falsa" após a expressão.

Exemplo:

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

exemplo de fim

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 de fim

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 regra a seguir se aplica a esses 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 base não indexadas (§12.8.14), typeof expressões (§12.8.17), expressões de valor padrão (§12.8.20), nameof expressões (§12.8.22) e expressões de declaração (§12.17).

  • O estado de atribuição definida 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 com expressões incorporadas

As seguintes regras se aplicam a esses 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.11), expressões de acesso base com indexação (§12.8.14), expressões de incremento e decremento (§12.8.15, §12.9.6), expressões de conversão (§12.9.7), expressões unárias, -, , ~, , * binárias++, , %<<=/>>*<<- >, >=, ==, !=, isas&|, expressões ^ (§12.10, §12.11, §12.12, §12.13), expressões de atribuição composta (§12.21.4) checked e unchecked expressões (§12.8.19), expressões de criação de matriz e delegado (§12.8.16) e await expressões (§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 de fim

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 definida 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 definida 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 objeto

Se o método a ser invocado for um método parcial que não tem nenhuma declaração de método parcial de implementação ou for um método condicional para o qual a chamada é omitida (§22.5.3.2), o estado de atribuição definida de v após a invocação será 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 chamada expr do formulário:

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

ou uma expressão de criação de objeto expr do formato:

new «type» ( «arg₁», «arg₂», … , «argₓ» )
  • Para uma expressão de invocação, o estado de atribuição definida 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 definida 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 de expressão normais, ignorando quaisquer inmodificadores , out, ou ref .
  • Para cada argumento argi para qualquer i maior que um, o estado de atribuição definida de v antes de argi é o mesmo que o estado de v após argi₋₁.
  • Se a variável v for passada como um out argumento (ou seja, um argumento da forma "out v") 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.16.5), inicializadores de objeto (§12.8.16.3), inicializadores de coleção (§12.8.16.4) e inicializadores de objeto anônimo (§12.8.16.7), o estado de atribuição definida é determinado pela expansão que esses constructos são definidos em termos.

9.4.4.25 Expressões de atribuição simples

Que o conjunto de destinos de atribuição em uma expressão e seja definido da seguinte maneira:

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

Para uma expressão expr do formato:

«expr_lhs» = «expr_rhs»
  • O estado de atribuição definida 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 for um destino de atribuição de expr_lhs, 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 de propriedade que designa P for um destino de atribuição de expr_lhs, 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 após arr[x = 1] ser avaliada como o lado esquerdo da segunda atribuição simples.

exemplo de fim

9.4.4.26 &; expressões

Para uma expressão expr do formato:

«expr_first» && «expr_second»
  • O estado de atribuição definida 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 é definitivamente atribuído se e somente se o estado de v após expr_first é definitivamente atribuído ou "definitivamente atribuído após expressão verdadeira". Caso contrário, não é definitivamente atribuído.
  • O estado de atribuição definida 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 expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após 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. Por outro lado, a variável i não é definitivamente atribuída na segunda instrução incorporada, pois x >= 0 pode ter testado false, resultando na não atribuição da variável i.

exemplo de fim

9.4.4.27 || Expressões

Para uma expressão expr do formato:

«expr_first» || «expr_second»
  • O estado de atribuição definida 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 é definitivamente atribuído se e somente se o estado de v após expr_first é definitivamente atribuído ou "definitivamente atribuído após expressão verdadeira". Caso contrário, não é definitivamente atribuído.
  • A instrução de atribuição definida 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 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 for 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. Por outro lado, a variável i não é definitivamente atribuída na primeira instrução incorporada, pois x >= 0 pode ter testado true, resultando na não atribuição da variável i.

exemplo de fim

9.4.4.28 ! expressões

Para uma expressão expr do formato:

! «expr_operand»
  • O estado de atribuição definida 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 definida de v após expr é determinado por:
    • Se o estado de depois de v expr_operand é definitivamente atribuído, então o estado de depois de v expr é definitivamente atribuído.
    • Caso contrário, se o estado de v after expr_operand for "definitivamente atribuído após expressão falsa", então o estado de v after expr será "definitivamente atribuído após expressão verdadeira".
    • Caso contrário, se o estado de v após expr_operand for "definitivamente atribuído após expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após expressão falsa".
    • Caso contrário, o estado de v after expr não será atribuído definitivamente.

9.4.4.29 ?? expressões

Para uma expressão expr do formato:

«expr_first» ?? «expr_second»
  • O estado de atribuição definida 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 instrução de atribuição definida 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 definida de v após expr_first.

9.4.4.30 ?: expressões

Para uma expressão expr do formato:

«expr_cond» ? «expr_true» : «expr_false»
  • O estado de atribuição definida de v antes de expr_cond é o mesmo que o estado de v antes de expr.
  • O estado de atribuição definida de v antes de expr_true é definitivamente atribuído se o estado de v após expr_cond for definitivamente atribuído ou "definitivamente atribuído após expressão verdadeira".
  • O estado de atribuição definida de v antes de expr_false é definitivamente atribuído se o estado de v após expr_cond for definitivamente atribuído ou "definitivamente atribuído após expressão falsa".
  • O estado de atribuição definida 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 é 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) corpo:

  • 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 definida 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 de tempo de compilação, pois max não é definitivamente atribuído onde a função anônima é declarada.

exemplo de fim

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 de tempo de compilação, pois a atribuição de n na função anônima não afeta o estado de atribuição definida de n fora da função anônima.

exemplo de fim

9.4.4.32 Expressões de lançamento

Para uma expressão expr do formato:

throwthrown_expr

  • O estado de atribuição definida de v antes de thrown_expr é o mesmo que o estado de v antes de expr.
  • O estado de atribuição definida 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 de delegado.

A atribuição definida para o corpo de cada função local é definida separadamente para cada site de chamada. Em cada chamada, 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 todos os pontos de controle que saem da 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 ao 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 reanalisados para atribuição definida em cada invocação de função local ou conversão de delegado. 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 implementaçã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 ler uma variável capturada antes de escrevê-la, a variável capturada deverá 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 for chamado antes de s ser definitivamente atribuído. F2 atribui antes de i lê-lo. Pode ser chamado antes de i ser 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 de fim

9.4.4.34 Expressões de padrão IS

Para uma expressão expr do formato:

expr_operand é padrão

  • O estado de atribuição definida 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' for declarada em 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 definido de 'v' após expr é o mesmo que o estado de atribuição definida de 'v' após expr_operand.

9.5 Referências de 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
    ;

Observação: em C e C++, um variable_reference é conhecido como lvalue. nota final

9.6 Atomicidade de referências variáveis

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

9.7 Variáveis de referência e retornos

9.7.1 Geral

Uma variável de referência é uma variável que se refere a outra variável, chamada de 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 o valor de 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 à qual uma variável de referência se refere, ou seja, a variable_reference armazenada para 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 de fim

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 de fim

9.7.2 Contextos seguros de referência

9.7.2.1 Geral

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 que o ref-safe-context de seu referente.

Observação: a noção relacionada de um 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 que seja pelo menos tão amplo quanto o contexto ref-safe da própria variável de referência.

Observação: o compilador determina o ref-safe-context por meio 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:

  • declaration-block: O ref-safe-context de um variable_reference a uma variável local (§9.2.9) é o escopo dessa variável local (§13.6.2), incluindo quaisquer instruçõesembutidas aninhadas 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 ref-safe-context 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; e
    • O parâmetro de referência implícita (ref) (§15.6.2.3.3) this de uma função de membro struct, juntamente com 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 de 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) diferentes do implícito this de uma função de membro struct;
    • Campos de membros e elementos de tais parâmetros;
    • Campos de membro de parâmetros do tipo de classe; e
    • 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 largo (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 que um referente seja a expressão de inicialização 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 de end.

Exemplo: para struct tipos, o parâmetro implícito this é passado como um parâmetro de referência. O ref-safe-context dos campos de um struct tipo como membro da função impede o retorno desses campos por retorno de referência. Essa 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 de end.

9.7.2.2 Contexto seguro de referência de variável local

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 de inicialização.
  • Caso contrário, seu ref-safe-context é declaration-block.

9.7.2.3 Contexto seguro de referência de parâmetro

Para um parâmetro p:

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

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 será 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 de 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 todas as refexpressões , out, e in argumento (excluindo o receptor).
  • Para cada parâmetro de entrada, se houver uma expressão correspondente que seja uma variável e houver 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 delimitador mais próximo.
  • O contexto seguro (§16.4.12) de todas as expressões de argumento (incluindo o receptor).

Exemplo: o último marcador é necessário para lidar com código 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 de fim

Uma invocação de propriedade e uma invocação de indexador (ou get set) 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 delimitador mais próximo.

Observação: isso ocorre em uma invocação como M(ref d.Length) where d is 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 como retornando o tipo que está sendo construído.

9.7.2.9 Limitações nas 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 por 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 ref-safe-context de e2 deve ser pelo menos um contexto tão amplo quanto o ref-safe-context de e1.
  • Para uma instrução return ref e1ref return, o ref-safe-context de e1 deve ser o caller-context.