Erick Dutra
Sep 8, 2018 · 26 min read

C# Zero To Hero — Variáveis

Variáveis representam locais de armazenamento. Cada variável possui um tipo que determina quais valores podem ser armazenados na variável. C# é uma linguagem de segurança de tipos e o compilador C# garante que os valores armazenados em variáveis sejam sempre do tipo apropriado. O valor de uma variável pode ser alterado por meio de atribuição ou pelo uso dos operadores ++ e -.
Uma variável deve ser definitivamente atribuída antes que seu valor possa ser obtido.
Conforme descrito nas seções 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 possui valor inicial. Para uma variável inicialmente não atribuída ser considerada definitivamente atribuída em um determinado local, uma atribuição à variável deve ocorrer em todo caminho de execução possível que leva a esse local.

Categorias de Variáveis

C# possui sete categorias de variáveis: variáveis estáticas, variáveis de instância, elementos de matriz, parâmetros de valor, parâmetros de referência, parâmetros de saída e variáveis locais. As seções a seguir descrevem cada uma dessas categorias.
No exemplo:

class A 
{
public static int x;
int y;
void F(int[] v, int a, ref int b, out int c)
{
int i = 1;
c = a + b++;
}
}

‘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 e ‘i’ é uma variável local.

Variáveis Estáticas

Um campo declarado com o modificador estático é chamado de variável estática. Uma variável estática passa a existir antes da execução do construtor estático para seu tipo de contêiner 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 do tipo da variável.
Para fins de verificação de atribuição definida, uma variável estática é considerada inicialmente atribuída.

Variáveis de instância

Um campo declarado sem o modificador estático é chamado de variável de instância.

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 destrutor da instância (se houver) foi executado.
O valor inicial de uma variável de instância de uma classe é o valor padrão 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.

Variáveis de instância em estruturas

Uma variável de instância de uma estrutura tem exatamente a mesma duração que a variável struct à qual ela pertence. Em outras palavras, quando uma variável de um tipo de estrutura passa a existir ou deixa de existir, o mesmo acontece com as variáveis de instância da estrutura.
O estado de atribuição inicial de uma variável de instância de uma estrutura é o mesmo da variável struct contida. Em outras palavras, quando uma variável struct é considerada inicialmente atribuída, assim também são suas variáveis de instância, e quando uma variável struct é considerada inicialmente não atribuída, suas variáveis de instância também não são atribuídas.

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 ocorrência de matriz.
O valor inicial de cada um dos elementos de uma matriz é o valor padrão do tipo dos elementos da matriz.
Para fins de verificação de atribuição definida, um elemento de matriz é considerado inicialmente atribuído.

Parâmetros de valor

Um parâmetro declarado sem um modificador ref ou out é um parâmetro de valor.
Um parâmetro de valor passa a existir na invocação do membro da função (método, construtor da instância, acessador ou operador) ou da função anônima à qual o parâmetro pertence, e é inicializado com o valor do argumento fornecido na chamada. Um parâmetro de valor normalmente deixa de existir no retorno do membro da função ou da função anônima. No entanto, se o parâmetro value for capturado por uma função anônima, seu tempo de vida se estenderá pelo menos até que a árvore de delegação ou expressão criada a partir dessa função anônima seja elegível para coleta de lixo.
Para fins de verificação de atribuição definida, um parâmetro de valor é considerado inicialmente atribuído.

Parâmetros de referência

Um parâmetro declarado com um modificador ‘ref’ é um parâmetro de referência.
Um parâmetro de referência não cria um novo local de armazenamento. Em vez disso, um parâmetro de referência representa o mesmo local de armazenamento que a variável fornecida como argumento no membro da função ou chamada de função anônima. Assim, o valor de um parâmetro de referência é sempre o mesmo que a variável subjacente.
As seguintes regras de atribuição definidas se aplicam aos parâmetros de referência. Observe as regras diferentes para os parâmetros de saída descritos em Parâmetros de saída.

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

Em um método de instância ou acessador de instância de um tipo struct, a palavra-chave this se comporta exatamente como um parâmetro de referência do tipo struct.

Parâmetros de saída

Um parâmetro declarado com um modificador ‘out’ é um parâmetro de saída.
Um parâmetro de saída não cria um novo local de armazenamento. Em vez disso, um parâmetro de saída representa o mesmo local de armazenamento que a variável fornecida como argumento no membro da função ou chamada de delegado. Assim, o valor de um parâmetro de saída é sempre o mesmo que a variável subjacente.
As seguintes regras de atribuição definidas se aplicam aos parâmetros de saída. Observe as diferentes regras para os parâmetros de referência descritos em Parâmetros de referência.

  • Uma variável não precisa ser atribuída definitivamente antes de poder ser passada como um parâmetro de saída em um membro de função ou chamada de delegado.
  • Após a conclusão normal de um membro de função ou chamada de delegado, cada variável transmitida 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.
  • Todo parâmetro de saída de um membro de função ou função anônima deve ser definitivamente atribuído antes que o membro da função ou função anônima retorne normalmente.

Em um construtor de instância de um tipo struct, a palavra-chave ‘this’ se comporta exatamente como um parâmetro de saída do tipo struct.

Variáveis locais

Uma variável local é declarada por uma declaração_variável_local, que pode ocorrer em um bloco, uma instrução for_statement, uma instrução_de_interrupção ou uma instrução_utilização; ou por uma foreach_statement ou uma specific_catch_clause para uma try_statement.
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 pelo menos desde a entrada no bloco, for_statement, switch_statement, using_statement, foreach_statement ou specific_catch_clause com o qual está associado, até que a execução desse bloco, for_statement, switch_statement, using_statement, foreach_statement ou specific_catch_clause, termine de alguma forma. (Inserir um bloco fechado ou chamar um método suspende, mas não termina, a execução do bloco atual, for_statement, switch_statement, using_statement, foreach_statement ou specific_catch_clause.) Se a variável local for capturada por uma função anônima, seu tempo de vida estende 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, seja elegível para coleta de lixo.
Se o bloco pai, for_statement, switch_statement, using_statement, foreach_statement ou specific_catch_clause for inserido recursivamente, uma nova instância da variável local será criada a cada vez e seu local_variable_initializer, se houver, será avaliado a cada vez.
Uma variável local introduzida por um local_variable_declaration não é inicializada automaticamente e, portanto, não possui valor padrão. Para fins de verificação de atribuição definida, uma variável local introduzida por uma declaração_variable_ local é considerada inicialmente não atribuída. Um local_variable_declaration pode incluir um local_variable_initializer, caso em que a variável é considerada definitivamente atribuída somente após a expressão de inicialização (declarações de declaração).
Dentro do escopo de uma variável local introduzida por uma declaração_variable_ local, é um erro em tempo de compilação referir-se a essa variável local em uma posição textual que precede seu local_variable_declarator. Se a declaração de variável local é implícita, também é um erro referir-se à variável dentro de seu local_variable_declarator.
Uma variável local introduzida por foreach_statement ou specific_catch_clause é considerada definitivamente atribuída em todo o seu escopo.
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 que seu bloco contendo.
O armazenamento referido por uma variável de referência local é recuperado independentemente do tempo de vida dessa variável de referência local.

Valores padrão

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

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

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

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

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

Atribuição definitiva

Em um determinado local no código executável de um membro da função, uma variável é definida como definitivamente atribuída se o compilador puder provar, por uma análise de fluxo estática específica, que a variável foi inicializada automaticamente ou tem sido alvo de pelo menos uma tarefa. Informalmente declarado, as regras de designação definida são:

  • Uma variável inicialmente designada é sempre considerada definitivamente atribuída.
  • Uma variável inicialmente não atribuída é 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 na qual a variável é o operando esquerdo.Uma expressão de chamada ou expressão de criação de objeto que transmite a variável como um parâmetro de saída.Para uma variável local, uma declaração de variável local que inclui um inicializador de variáveis.

A especificação formal subjacente às regras informais acima é descrita em Variáveis atribuídas inicialmente, Variáveis inicialmente não atribuídas e Regras precisas para determinar a atribuição definida.
Os estados de atribuição definidos das variáveis de instância de uma variável struct_type são rastreados individualmente e coletivamente. Além das regras acima, as seguintes regras se aplicam a variáveis struct_type e suas variáveis de instância:

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

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

  • Uma variável deve ser definitivamente atribuída em cada local onde seu valor é obtido. Isso garante que valores indefinidos nunca ocorram. Considera-se a ocorrência de uma variável em uma expressão 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 oua 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 localidade onde é passada como um parâmetro de referência. Isso garante que o membro da função que está sendo chamado considere o parâmetro de referência inicialmente designado.
  • 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 até a execução atingir o final do corpo do membro da função). Isso garante que os membros da função não retornem valores indefinidos em parâmetros de saída, permitindo, assim, que o compilador considere uma chamada de membro de função que toma uma variável como um parâmetro de saída equivalente a uma atribuição à variável.
  • A variável this de um construtor de instância struct_type deve ser definitivamente atribuída em cada local onde esse construtor de instância retorna.

Variáveis inicialmente atribuídas

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

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

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 variável this de construtores de instância de struct.
  • Variáveis locais, exceto aquelas declaradas em uma cláusula catch ou uma instrução foreach.

Regras precisas para determinar a atribuição definida

Para determinar se cada variável usada é definitivamente atribuída, o compilador deve usar um processo equivalente ao descrito nesta seção.
O compilador processa o corpo de cada membro da função que possui uma ou mais variáveis inicialmente não designadas. Para cada variável inicialmente não atribuída v, o compilador determina um estado de atribuição definido para v em cada um dos seguintes pontos no membro da função:

  • No início de cada declaração
  • No ponto final (pontos finais e alcançabilidade) de cada declaração
  • Em cada arco que transfere o controle para outra declaração ou para o ponto final de uma declaração
  • No começo de cada expressão
  • No final de cada expressão

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

  • Definitivamente atribuído. Isso indica que em todos os fluxos de controle possíveis até este ponto, v foi atribuído 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 é atribuída definitivamente pode (mas não necessariamente) cair em um dos seguintes subestados:
  • Definitivamente atribuído após a expressão verdadeira. Esse estado indica que v é definitivamente atribuído se a expressão booleana for avaliada como verdadeira, mas não será necessariamente designada 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 é necessariamente designada se a expressão booleana for avaliada como verdadeira.

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

Regras gerais para declarações

  • v definitivamente não é atribuído no início de um corpo de membro de função.
  • v é definitivamente atribuído no início de qualquer declaração inacessível.
  • O estado de atribuição definido de v no início de qualquer outra instrução é determinado pela verificação do estado de atribuição definido de v em todas as transferências de fluxo de controle que visam o início dessa instrução. Se (e somente se) v for definitivamente atribuído a todas as transferências de fluxo de controle, então v será definitivamente atribuído no início da instrução. O conjunto de transferências de fluxo de controle possíveis é determinado da mesma maneira que para a verificação da acessibilidade da instrução.
  • O estado de atribuição definido de v no ponto final de um bloco, marcado, desmarcado, if, while, do, foreach, lock, using ou switch é determinado pela verificação do estado de atribuição definido de v em todas as transferências de fluxo de controle que tem como alvo o ponto final dessa declaração. Se v é definitivamente atribuído em todas as transferências de fluxo de controle, então v é definitivamente atribuído no ponto final da instrução. De outra forma, v definitivamente não é atribuído no ponto final da instrução. O conjunto de transferências de fluxo de controle possíveis é determinado da mesma maneira que para a verificação da acessibilidade da instrução.

Declarações de bloqueio, verificadas e desmarcadas

O estado de atribuição definido de v na transferência de controle para a primeira declaraçã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 declaração de bloco, marcada ou desmarcada.

Declarações de expressão

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

  • v tem o mesmo estado de atribuição definido no início de expr como no início de stmt.
  • Se v se definitivamente atribuído no final de expr, é definitivamente atribuído no ponto final de stmt; de outra forma; não é definitivamente atribuído no ponto final do stmt.

Declarações de instrução

  • Se stmt é uma instrução de declaração sem inicializadores, então v tem o mesmo estado de atribuição definido no ponto final de stmt como no início de stmt.
  • Se stmt é uma instrução de declaração com inicializadores, o estado de atribuição definido para v é determinado como se 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).

Declaração If

Para uma declaração if stmt na forma:

if (expr) then_stmt else else_stmt
  • v tem o mesmo estado de atribuição definido no início de expr como no início de stmt.
  • Se v é definitivamente atribuído no final de expr, então é definitivamente atribuído na transferência de fluxo de controle para then_stmt e para else_stmt ou para o ponto final de stmt se não houver mais nenhuma cláusula.
  • Se v tem o estado “definitivamente atribuído após a expressão verdadeira” no final de expr, então é definitivamente atribuído na transferência de fluxo de controle para then_stmt, e não definitivamente atribuído na transferência de fluxo de controle para else_stmt ou para o ponto final de stmt se não houver mais cláusula.
  • Se v tem o estado “definitivamente atribuído após falsa expressão” no final de expr, então é definitivamente atribuído na transferência de fluxo de controle para else_stmt, e não definitivamente atribuído na transferência de fluxo de controle para then_stmt. 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 não é considerado definitivamente atribuído na transferência de fluxo de controle para o then_stmt ou else_stmt ou para o terminal de stmt se não houver mais nenhuma cláusula.

Declaração Switch

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

  • O estado de atribuição definido de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definido de v na transferência de fluxo de controle para uma lista de instrução de bloco de comutador alcançável é o mesmo que o estado de atribuição definido de v no final de expr.

Declaração While

Para uma declaração while stmt na forma:

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

Declaração Do

Para uma declaração do stmt na forma:

do do_body while (expr);
  • v tem o mesmo estado de atribuição definido na transferência do fluxo de controle desde o início do estado até o corpo, como no início do stmt.
  • v tem o mesmo estado de atribuição definido no início de expr como no ponto final de do_body.
  • Se v é definitivamente atribuído no final de expr, então é definitivamente atribuído na transferência de fluxo de controle para o ponto final do stmt.
  • Se v tem o estado “definitivamente atribuído após falsa expressão” no final de expr, então ele é definitivamente atribuído na transferência de fluxo de controle para o ponto final de stmt.

Declaração For

Para uma declaração for na forma:

for (for_initializer;for_condition;for_iterator) embedded_statement

é feito como se a declaração fosse escrita:

{
for_initializer; while (for_condition){embedded_statement;
for_iterator; }
}

Se for_condition for omitido da instrução for, a avaliação da atribuição definida prosseguirá como se for_condition fosse substituído por true na expansão acima.

Declaração Break, continue e goto

O estado de atribuição definido 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 definido de v no início da instrução.

Declaração Throw

Para uma declaração stmt da forma:

throw expr;

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

Declaração Return

Para uma declaração stmt da forma:

return expr;
  • he definite assignment state of v at the beginning of expr is the same as the definite assignment state of v at the beginning of stmt.
  • If v is an output parameter, then it must be definitely assigned either:
depois de exprou no final do bloco finally de um try-finally ou try-catch-finally que inclui a declaração de retorno.

Declaração Try-catch

Para uma declaração stmt da forma:

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

Declaração Try-finally

Para uma declaração try stmt da forma:

try try_block finally finally_block
  • The definite assignment state of v at the beginning of try_block is the same as the definite assignment state of v at the beginning of stmt.
  • The definite assignment state of v at the beginning of finally_block is the same as the definite assignment state of v at the beginning of stmt.
  • The definite assignment state of v at the end-point of stmt is definitely assigned if (and only if) at least one of the following is true:
v is definitely assigned at the end-point of try_blockv is definitely assigned at the end-point of finally_block

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

Declaração Try-catch-finally

Para uma declaração try-catch-finally stmt da forma:

try try_block 
catch(...) catch_block_1
...
catch(...) catch_block_n
finally *finally_block*

é feito como se a instrução fosse uma instrução try-finally anexando uma instrução try-catch:

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

O exemplo a seguir demonstra como os diferentes blocos de uma instrução try 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
}
}

Declaração Foreach

Para uma declaração foreach stmt da forma:

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

Declaração Using

Para uma declaração using stmt da forma:

using ( resource_acquisition ) embedded_statement
  • O estado de atribuição definido 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 definido de v na transferência de fluxo de controle para embedded_statement é o mesmo que o estado de v no final de resource_acquisition.

Declaração Lock

Para uma declaração lock stmt da forma:

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

Declaração Yield

Para uma declaração yield stmt da forma:

yield return expr ;
  • O estado de atribuição definido de v no início de expr é o mesmo que o estado de v no início de stmt.
  • O estado de atribuição definido de v no final de stmt é o mesmo que o estado de v no final de expr.
  • Uma instrução ‘yield’ não tem efeito sobre o estado de atribuição definido.

Regras gerais para expressões simples

A seguinte regra se aplica a esses tipos de expressões: literais, nomes simples (nomes simples), expressões de acesso a membros, expressões de acesso básico não indexadas, tipo de expressões, padrão expressões de valor e nome de expressões.

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

Regras gerais para expressões com expressões incorporadas

As seguintes regras se aplicam a esses tipos de expressões: expressões com parêntesis, expressões de acesso a elementos, expressões de acesso base com indexação, expressões de incremento e decremento, expressões de Cast, expressões de atribuição compostas, expressões marcadas e não verificadas, além de expressões de criação de matriz e delegação.
Cada uma dessas expressões possui uma ou mais subexpressões que são avaliadas incondicionalmente em uma ordem fixa. Por 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, na ordem da esquerda para a direita. Para uma expressão expr, que possui subexpressões e1, e2, …, eN, avaliadas nessa ordem:

  • O estado de atribuição definido de v no início de e1 é o mesmo que o estado de atribuição definido no início de expr.
  • O estado de atribuição definido de v no início de ei (i maior que um) é o mesmo que o estado de atribuição definido no final da subexpressão anterior.
  • O estado de atribuição definido de v no final de expr é o mesmo que o estado de atribuição definido no final de eN

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

Exemplo:

primary_expression ( arg1 , arg2 , ... , argN )

ou uma expressão de criação de objeto:

new type ( arg1 , arg2 , ... , argN )
  • 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 arg1 é o mesmo que o estado de v após a expressão_principal.
  • Para uma expressão de criação de objeto, o estado de atribuição definido de v antes de arg1 é 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 modificadores ref ou out.
  • Para cada argumento argi para qualquer i maior que um, o estado de atribuição definido de v antes de argi é o mesmo que o estado de v após o arg anterior.
  • Se a variável v é passada como um argumento out (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. De outra forma; o estado de v depois de expr é o mesmo que o estado de v depois de argN.
  • Para inicializadores de array, inicializadores de objeto, inicializadores de coleção e inicializadores de objetos anônimos, o estado de atribuição definido é determinado pela expansão que essas construções são definidas em termos de .

Expressões de atribuição simples

Para uma expressão expr da forma w = expr_rhs:

O estado de atribuição definido de v antes de expr_rhs é o mesmo que o estado de atribuição definido de v antes de expr.

O estado de atribuição definido de v após expr é determinado por:

  • Se w é a mesma variável que v, então o estado de atribuição definido 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, se w for um acesso de propriedade designando uma propriedade implementada automaticamente P na instância sendo construída e v for o campo de backup oculto de P, então o estado de atribuição definido de v após expr é definitivamente atribuído.
  • Caso contrário, o estado de atribuição definido de v após expr é o mesmo que o estado de atribuição definido de v após expr_rhs.

Expressões && (condicional AND)

Para uma expressão expr da forma expr_first && expr_second:

O estado de atribuição definido de v antes de expr_first é o mesmo que o estado de atribuição definido de v antes de expr.
O estado de atribuição definido de v antes de expr_second é definitivamente atribuído se o estado de v após expr_first for definitivamente atribuído ou “definitivamente designado após a expressão verdadeira”. Caso contrário, não é definitivamente atribuído.
O estado de atribuição definido de v após expr é determinado por:

  • Se expr_first for uma expressão constante com o valor false, o estado de atribuição definido de v após expr será o mesmo que o estado de atribuição definido de v após expr_first.
  • Caso contrário, se o estado de v após expr_first for definitivamente atribuído, o estado de v após expr será definitivamente atribuído.
  • Caso contrário, se o estado de v após expr_second for definitivamente atribuído, e o estado de v após expr_first for “definitivamente atribuído após uma expressão falsa”, o estado de v após expr será 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 a expressão verdadeira”, o estado de v após expr será “definitivamente atribuído após a expressão verdadeira”.
  • Caso contrário, se o estado de v após expr_first for “definitivamente atribuído após uma expressão falsa”, e o estado de v após expr_second for “definitivamente atribuído após uma expressão falsa”, o estado de v após expr será “definitivamente atribuído após uma expressão falsa”.
  • Caso contrário, o estado de v depois de expr não é atribuído definitivamente.

No exemplo

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 embutidas de uma instrução if, mas não na outra. Na instrução if no método F, a variável i é definitivamente atribuída na primeira instrução incorporada porque a execução da expressão (i = y) sempre precede a execução dessa instrução incorporada. Em contraste, a variável i não é definitivamente atribuída na segunda instrução incorporada, uma vez que x> = 0 pode ter sido testada como falsa, resultando na não atribuição da variável i.

Expressões || (condicional OU)

Para uma expressão expr da forma expr_first || expr_second:

O estado de atribuição definido de v antes de expr_first é o mesmo que o estado de atribuição definido de v antes de expr.

O estado de atribuição definido de v antes de expr_second é definitivamente atribuído se o estado de v após expr_first for definitivamente atribuído ou “definitivamente designado após uma expressão falsa”. Caso contrário, não é definitivamente atribuído.

A declaração de atribuição definida de v após expr é determinada por:

  • Se expr_first for uma expressão constante com o valor true, o estado de atribuição definido de v após expr será o mesmo que o estado de atribuição definido de v após expr_first.
  • Caso contrário, se o estado de v após expr_first for definitivamente atribuído, o estado de v após expr será definitivamente atribuído.
  • Caso contrário, se o estado de v após expr_second for definitivamente atribuído, e o estado de v após expr_first for “definitivamente atribuído após a expressão verdadeira”, o estado de v após expr será 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 uma expressão falsa”, o estado de v após expr será “definitivamente atribuído após uma expressão falsa”.
  • Caso contrário, se o estado de v após expr_first for “definitivamente atribuído após a expressão verdadeira”, e o estado de v após expr_second for “definitivamente atribuído após a expressão verdadeira”, o estado de v após expr será “definitivamente atribuído após a expressão verdadeira”.
  • Caso contrário, o estado de v depois de expr não é atribuído definitivamente.

No exemplo

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 embutidas de uma instrução if, mas não na outra. Na instrução if no método G, a variável i é definitivamente atribuída na segunda instrução incorporada, porque a execução da expressão (i = y) sempre precede a execução dessa instrução incorporada. Em contraste, a variável i não é definitivamente atribuída na primeira instrução incorporada, uma vez que x> = 0 pode ter sido testada como true, resultando na não atribuição da variável i.

Expressões ! (negação lógica)

Para uma expressão expr da forma !expr_operand:

O estado de atribuição definido de v antes de expr_operand é o mesmo que o estado de atribuição definido de v antes de expr.

O estado de atribuição definido de v após expr é determinado por:

  • Se o estado de v após expr_operand * for definitivamente atribuído, o estado de * v após expr será definitivamente atribuído.
  • Se o estado de v após expr_operand * não for definitivamente atribuído, o estado de * v após expr não será atribuído definitivamente.
  • Se o estado de v após expr_operand * for “definitivamente atribuído após falsa expressão”, então o estado de * v após expr será “definitivamente atribuído após a expressão verdadeira”.
  • Se o estado de v após expr_operand * é “definitivamente atribuído após a expressão verdadeira”, então o estado de * v após expr é “definitivamente atribuído após uma expressão falsa”.

Expressões ?? (coalescência nula)

Para uma expressão expr da forma expr_first ?? expr_second:

O estado de atribuição definido de v antes de expr_first é o mesmo que o estado de atribuição definido de v antes de expr.

O estado de atribuição definido de v antes de expr_second é o mesmo que o estado de atribuição definido de v após expr_first.

A declaração de atribuição definida de v após expr é determinada por:

  • Se expr_first for uma expressão constante (Expressões constantes) com valor nulo, 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 definido de v após expr_first.

?: expressões (condicionais)

Para uma expressão expr da forma expr_cond ? expr_true: expr_false:

O estado de atribuição definido de v antes de expr_cond é o mesmo que o estado de v antes de expr.

O estado de atribuição definido de v antes de expr_true é definitivamente atribuído se, e somente se, um dos seguintes itens se mantiver:

  • expr_cond é uma expressão constante com o valor false
  • o estado de v após expr_cond é definitivamente atribuído ou “definitivamente atribuído após a expressão verdadeira”.

O estado de atribuição definido de v antes de expr_false é definitivamente atribuído se, e somente se, um dos seguintes itens se mantiver:

  • expr_cond é uma expressão constante com o valor true
    o estado de v após expr_cond é definitivamente atribuído ou “definitivamente atribuído após falsa expressão”.

O estado de atribuição definido de v após expr é determinado por:

  • Se expr_cond for uma expressão constante (expressões constantes) 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 (Expressões constantes) 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 for definitivamente atribuído e o estado de v após expr_false for definitivamente atribuído, o estado de v após expr será definitivamente atribuído.
  • Caso contrário, o estado de v depois de expr não é atribuído definitivamente.

Funções anônimas

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

  • O estado de atribuição definido de uma variável externa v antes de 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.

No exemplo

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

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

delegate void D();  void F() {     
int n;
D d = () => { n = 1; };

d();
// Error, n is not definitely assigned
Console.WriteLine(n);
}

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

Varáveis de Referência

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

variable_reference: expression;

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

Atomicidade de variáveis de referências

As leituras e gravações dos seguintes tipos de dados são atômicas: bool, char, byte, sbyte, short, ushort, uint, int, float e tipos de referência. Além disso, leituras e gravações de tipos enum com um tipo subjacente na lista anterior também são atômicas. Leituras e gravações de outros tipos, incluindo tipos longos, ulong, double e decimal, bem como tipos definidos pelo usuário, não têm garantia de serem atômicas. Além das funções de biblioteca projetadas para essa finalidade, não há garantia de leitura atômica-modificação-gravação, como no caso de incremento ou decremento.

    Erick Dutra

    Written by

    Analista de Sistemas & Software Developer (Sênior)

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade