Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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 eié uma variável local. fim do exemplo
9.2.2 Variáveis estáticas
Um campo declarado com o modificador static é 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 modificador static é 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 struct que o contém. 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 de valor for capturado por uma função anônima (§12.21.6.2), seu tempo de vida se estenderá pelo menos até que a árvore delegada ou 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). fim da observação
- 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 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 na 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.
Observação: as regras para parâmetros de saída são diferentes e estão descritas em (§9.2.6). fim da observação
- 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.
- Dentro de 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 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 entrada, 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 entrada.
- Uma variável deve ser definitivamente atribuída (§9.4) antes de poder ser passada como um parâmetro de entrada em uma invocação 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 adiante em §15.6.2.3.2.
9.2.9 Variáveis locais
9.2.9.1 Geral
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ãos (§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 outargument_value e como um tuple_element que é o destino de uma atribuição de desconstrução (§12.23.2).
O tempo de vida de uma variável local é a parte da execução do programa durante a qual é garantido que o armazenamento seja 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 fechado, chamar um método ou gerar um valor de um bloco de iterador suspende, mas não termina, a execução do escopo atual.) Se a variável local for capturada por uma função anônima (§12.21.6.2), seu tempo de vida se estenderá pelo menos até que a árvore delegada ou 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 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. fim da observação
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. fim da observação
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 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).
fim da observação
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 assignedDentro 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.
fim da observação
9.2.9.2 Descartes
Um descarte é uma variável local que não tem nome. Um descarte é introduzido por uma expressão de declaração (§12.19) com o identificador _; e é digitado implicitamente (_ ou var _) ou explicitamente tipado (T _).
Nota:
_é um identificador válido em muitas formas de declarações. fim da observação
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. fim da observação
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 deMmostra as diferentes formas de descartes disponíveis em tuplas e como parâmetros de saída.fim do exemplo
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. fim da observação
9.4 Atribuição definida
9.4.1 Geral
Em um determinado local no código executável de um membro da função ou uma função anônima, uma variável será atribuída definitivamente se um 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 o destino 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.23.2) na qual 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 é descrita em §9.4.2, §9.4.3 e §9.4.4.
fim da observação
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. fim da observação
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. fim da observação
Uma variável deve ser definitivamente atribuída em cada local onde é passada como um parâmetro de entrada.
Observação: isso garante que o membro da função que está sendo chamado possa considerar o parâmetro de entrada inicialmente atribuído. fim da observação
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 um 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. fim da observação
A variável
thisde 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
catchcláusula ouforeachinstrução.
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 não atribuídas inicialmente.
- Parâmetros de saída, incluindo a
thisvariável de construtores de instância struct sem um inicializador de construtor. - Variáveis locais, exceto aquelas declaradas em uma
catchcláusula ou instruçãoforeach.
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, 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 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 instrução
block,checked,unchecked,if,while,do,for,foreach,lock,using, ouswitché determinado pela verificação do estado de atribuição definitiva de v em todas as transferências de fluxo de controle direcionadas ao ponto final 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 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. fim da observação
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 Instruçõ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 um 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; }fim do exemplo
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
defaultró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 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; … } }fim do exemplo
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 tem 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 será 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 Instruções For
Para uma instruçã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 definitiva de v na transferência de fluxo de controle causada por uma instrução break, continue ou goto é o mesmo que o estado de atribuição definitiva de v no início do comando.
9.4.4.12 Instruções Throw
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 definitiva de v no início de stmt.
9.4.4.13 Instruções Return
Para uma declaração stmt do formulário:
return «expr» ;
- O estado de atribuição definitiva de v no início de expr é o mesmo que o estado de atribuição definitiva 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
finallybloco de umtry-finallyoutry-catch-finallyque inclui areturninstruçã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
finallybloco de umtry-finallyoutry-catch-finallyque inclui areturninstruçã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 do 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 definitiva 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 do stmt.
- O estado de atribuição definitiva de v no início de finally_block é o mesmo que o estado de atribuição definitiva 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 instruçã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
tryinstruçã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 } }fim do exemplo
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 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 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 Lock
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 InstruçõesYield
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 instrução yield break 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 subcláusulas que possam se aplicar.
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); }fim do exemplo
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); }fim do exemplo
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..7) 8.15), typeof expressões (§12.8.18), expressões de valor padrão (§12.8.21), nameof expressões (§12.8.23) e expressões de declaração (§12.19).
- 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 regras a seguir se aplicam a esses tipos de expressões: expressões parênteses (§12.8.5), expressões de tupla (§12.8.6), expressões de acesso de elemento (§12.8.12), expressões de acesso base com indexação (§12.8.15), expressões de incremento e decremento (§12.8.16, §12.9.7), expressões de conversão (§12.9.8), unário+, -, , ~* expressões, binário+, , -*, , /, %, <<, >>, <, , <=, , >>=, ==, !=, is, as, , &, |expressões ^ (§12.12, §12.13, §12.14, §12.15), expressões de atribuição composta (§12.23.4) checked e unchecked expressões (§12.8.20), expressões de criação de matriz e delegado (§12.8.17) e await expressões (§12.9.9).
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. fim do exemplo
Para uma expressão expr, que tem subexpressões expr₁, expr₂, ..., exprₓ, avaliadas nessa ordem:
- O estado de atribuição definitiva 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 é um método parcial que não tem nenhuma declaração de método parcial em implementação ou é um método condicional para o qual a chamada é omitida (§23.5.3.2), o estado de atribuição definitiva de v após a invocação é o mesmo que o estado de atribuição definitiva de v antes da invocação. Caso contrário, aplicam-se as seguintes regras:
Para uma expressão de invocação expr do formato:
«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 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 argᵢ, o estado de atribuição definitiva de v após argᵢ é determinado pelas regras de expressão normal, ignorando qualquer
in,out, ou modificadoresref. - Para cada argumento argᵢ para qualquer i maior que um, o estado de atribuição definitiva de v antes de argᵢ é o mesmo que o estado de v após argᵢ₋₁.
- Se a variável v for passada como um
outargumento (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.17.4), inicializadores de objeto (§12.8.17.2.2), inicializadores de coleção (§12.8.17.2.3) e inicializadores de objetos anônimos (§12.8.17.3), o estado de atribuição definitiva é determinado pela expansão da qual esses construtos são definidos em termos de.
9.4.4.25 Expressões de atribuição simples
Seja o conjunto de destinos de atribuição em uma expressão e ser definido como segue:
- 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 definitiva de v antes de expr_lhs é o mesmo que o estado de atribuição definitiva de v antes de expr.
- O estado de atribuição definitiva de v antes de expr_rhs é o mesmo que o estado de atribuição definitiva de v depois de 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 de struct e v for o campo de backup oculto de uma propriedade P implementada automaticamente na instância que está sendo construída, e um acesso de propriedade que designa P é um destino de atribuição de expr_lhs, o estado de atribuição definitiva de v após expr é definitivamente atribuído. Caso contrário, o estado de atribuição definitiva de v após expr é o mesmo que o estado de atribuição definitiva 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ósarr[x = 1]ser avaliada como o lado esquerdo da segunda atribuição simples.fim do exemplo
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 for 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 definitivo 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_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 após uma 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 umaifinstrução, mas não na outra. Na instruçãoifno métodoF, a variávelié 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ávelinão é definitivamente atribuída na segunda instrução incorporada, poisx >= 0pode ter testado false, resultando na não atribuição da variáveli.fim do exemplo
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 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 expressão falsa". Caso contrário, não é definitivamente atribuído.
- A instrução 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 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, e o estado de v após expr_second é "definitivamente atribuído após expressão verdadeira", então o estado de v após expr é "definitivamente atribuído após uma 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 umaifinstrução, mas não na outra. Na instruçãoifno métodoG, a variávelié 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ávelinão é definitivamente atribuída na primeira instrução incorporada, poisx >= 0pode ter testado verdadeiro, resultando na não atribuição da variáveli.fim do exemplo
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 definitivo de v após expr determinado por:
- Se o estado de
vapós expr_operand for definitivamente atribuído, então o estado devapós expr será definitivamente atribuído. - Caso contrário, se o estado de
vafter expr_operand for "definitivamente atribuído após expressão falsa", então o estado devafter expr será "definitivamente atribuído após expressão verdadeira". - Caso contrário, se o estado de
vapós expr_operand for "definitivamente atribuído após a expressão verdadeira", então o estado de v após expr será "definitivamente atribuído após falsa expressão". - Caso contrário, o estado de
vapós expr não é definitivamente atribuído.
- Se o estado de
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 definitiva de v antes de expr_second é o mesmo que o estado de atribuição definitiva de v depois de expr_first.
- A instrução de atribuição definida de v após expr determinado por:
- Se expr_first for uma expressão constante (§12,25) com valor
null, o estado de v após expr será 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.
- Se expr_first for uma expressão constante (§12,25) com valor
9.4.4.30 Expressões ?:
Para uma expressão expr do formato:
«expr_cond» ? «expr_true» : «expr_false»
- O estado de atribuição definitiva de v antes de expr_cond é o mesmo que o estado de atribuição definitiva de v antes de expr.
- O estado de atribuição definida de v antes de expr_true é definitivamente atribuído se e somente 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 e somente 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 definitivo de v após expr determinado por:
- Se expr_cond for uma expressão constante (§12,25) com valor
true, o estado de v após expr será o mesmo que o estado de v após expr_true. - Caso contrário, se expr_cond for uma expressão constante (§12.25) com valor
false, o estado de v após expr será o mesmo que o estado de v após expr_false. - Caso contrário, se o estado de v após expr_true é definitivamente atribuído, e o estado de v após expr_false é "definitivamente atribuído após expressão verdadeira", 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.
- Se expr_cond for uma expressão constante (§12,25) com valor
9.4.4.31 Funções anônimas
Para um lambda_expression ou anonymous_method_expressionexpr 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 definido de uma variável externa v antes de body é 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.
fim do exemplo
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
nna função anônima não afeta o estado de atribuição definida denfora da função anônima.fim do exemplo
9.4.4.32 Expressões Throw
Para uma expressão expr do formato:
throw
thrown_expr
- O estado de atribuição definitiva de v antes de thrown_expr é o mesmo que o estado de atribuição definitiva 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 local 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. fim da observação
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 local
F1lêssem atribuí-la. É um erro seF1for chamado antes desser definitivamente atribuído.F2atribui antes deilê-lo. Pode ser chamado antes deiser definitivamente atribuído. Além disso,F3pode ser chamado depoisF2porques2é definitivamente atribuído emF2.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); } }fim do exemplo
9.4.4.34 Expressões is-pattern
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 definitiva de v após expr é o mesmo que o estado de atribuição definitiva de v após expr_operand.
9.5 Referências de variáveis
Uma 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. fim da observação
9.6 Atomicidade de referências de 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 modificador ref.
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 de 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 } }fim do exemplo
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]; } }fim do exemplo
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.15), juntamente com restrições associadas. fim da observação
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.
Nota: um 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. fim da observação
Existem três ref-safe-contexts:
declaration-block: o ref-safe-context de uma variable_reference para uma variável local (§9.2.9.1) é o escopo dessa variável local (§13.6.2), incluindo quaisquer instruções incorporadass 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
thisde funções de membro de classe; e - O parâmetro de referência implícita (
ref) (§15.6.2.3.3)thisde 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.
- Parâmetros de valor (§15.6.2.2) em uma declaração de membro de função, incluindo o implícito
caller-context: dentro de uma função, uma 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
thisde 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.
- Parâmetros de referência (§9.2.6) diferentes do implícito
Uma 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 ref-safe-contexts. As declarações mostram o ref-safe-context para que um referente seja a expressão de inicialização de uma
refvariá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
structtipos, o parâmetro implícitothisé passado como um parâmetro de referência. O ref-safe-context dos campos de um tipostructcomo 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 final.
9.7.2.2 Contexto seguro de referência de variável local
Para uma variável local v:
- Se
vfor 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
pfor uma referência ou parâmetro de entrada, seu ref-safe-context será o caller-context. Sepfor um parâmetro de entrada, ele não poderá ser retornado como gravávelref, mas poderá ser retornado comoref readonly. - Se
pfor um parâmetro de saída, seu ref-safe-context será o caller-context. - Caso contrário, se
pfor o parâmetrothisde 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
efor de um tipo de referência, seu ref-safe-context será o caller-context. - Caso contrário, se
efor de um tipo de valor, seu ref-safe-context será o mesmo que o ref-safe-context dee.
9.7.2.5 Operadores
O operador condicional (§12.20) c ? ref e1 : ref e2e o operador de atribuição de referência (= ref e§12.23.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 operandos ref.
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 caller-context.
- O ref-safe-context de todas as
refexpressões ,out, einargumento (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.15) 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; }fim do exemplo
Uma invocação de propriedade e uma invocação de indexador (ou get ou 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)ondedé do tipodynamic. Também é consistente com os argumentos correspondentes aos parâmetros de entrada. fim da observação
9.7.2.8 Invocações de construtor
Uma expressão new 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
reflocal, nem um parâmetro ou local de um tiporef structdevem 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 tipo
ref structdevem ser um argumento para um método iterador ou um métodoasync. - Nem um
reflocal, nem um local de um tiporef structdevem estar no contexto no ponto de umayield returndeclaração ou expressãoawait. - Para uma reatribuição
e1 = ref e2de ref , o ref-safe-context dee2deve ser pelo menos um contexto tão amplo quanto o ref-safe-context dee1. - Para uma instrução
return ref e1ref return, o ref-safe-context dee1deve ser o caller-context.
ECMA C# draft specification