Se eu criar um bool dentro da minha classe, apenas algo como bool check
, o padrão será false.
Quando crio o mesmo bool no meu método bool check
(em vez de dentro da classe), recebo o erro "uso da verificação de variável local não atribuída". Por quê?
c#
language-design
local-variables
nachime
fonte
fonte
Respostas:
As respostas de Yuval e David estão basicamente corretas; Resumindo:
Um comentarista da resposta de David pergunta por que é impossível detectar o uso de um campo não atribuído por meio de análise estática; este é o ponto que desejo expandir nesta resposta.
Primeiro, para qualquer variável, local ou não, é praticamente impossível determinar exatamente se uma variável está atribuída ou não. Considerar:
A pergunta "é x atribuída?" é equivalente a "M () retorna true?" Agora, suponha que M () retorne verdadeiro se Último Teorema de Fermat for verdadeiro para todos os números inteiros menores que onze gajilhões e falso caso contrário. Para determinar se x é definitivamente atribuído, o compilador deve essencialmente produzir uma prova do Último Teorema de Fermat. O compilador não é tão inteligente.
Portanto, o que o compilador faz para os locais é implementar um algoritmo que é rápido e superestima quando um local não é definitivamente atribuído. Ou seja, possui alguns falsos positivos, onde diz "Não posso provar que este local está atribuído", mesmo que você e eu o reconheçamos. Por exemplo:
Suponha que N () retorne um número inteiro. Você e eu sabemos que N () * 0 será 0, mas o compilador não sabe disso. (Nota: o C # 2.0 do compilador fez saber que, mas eu removi que a otimização, como a especificação não dizer que o compilador sabe disso.)
Tudo bem, então o que sabemos até agora? É impraticável para os habitantes locais obterem uma resposta exata, mas podemos superestimar a não-atribuição a baixo custo e obter um resultado muito bom que pode ser do lado de "fazer você consertar seu programa pouco claro". Isso é bom. Por que não fazer a mesma coisa para os campos? Ou seja, faça um verificador de atribuição definido que superestime barato?
Bem, quantas maneiras existem para um local ser inicializado? Pode ser atribuído dentro do texto do método. Ele pode ser atribuído dentro de uma lambda no texto do método; que lambda nunca pode ser chamado, portanto, essas atribuições não são relevantes. Ou pode ser passado como "out" para outro método, quando podemos assumir que ele está atribuído quando o método retorna normalmente. Esses são pontos muito claros nos quais o local é designado e estão ali no mesmo método em que o local é declarado . Determinar a atribuição definida para os locais requer apenas análise local . Os métodos tendem a ser curtos - muito menos de um milhão de linhas de código em um método - e, portanto, analisar todo o método é bastante rápido.
Agora, e os campos? Os campos podem ser inicializados em um construtor, é claro. Ou um inicializador de campo. Ou o construtor pode chamar um método de instância que inicializa os campos. Ou o construtor pode chamar um método virtual que inicia os campos. Ou o construtor pode chamar um método em outra classe , que pode estar em uma biblioteca , que inicializa os campos. Campos estáticos podem ser inicializados em construtores estáticos. Os campos estáticos podem ser inicializados por outros construtores estáticos.
Essencialmente, o inicializador de um campo pode estar em qualquer lugar do programa inteiro , inclusive dentro de métodos virtuais que serão declarados em bibliotecas que ainda não foram gravadas :
É um erro compilar esta biblioteca? Se sim, como o BarCorp deve corrigir o erro? Atribuindo um valor padrão para x? Mas é isso que o compilador já faz.
Suponha que essa biblioteca seja legal. Se o FooCorp escrever
isso é um erro? Como o compilador deve descobrir isso? A única maneira é fazer uma análise completa do programa que rastreia a estática de inicialização de todos os campos em todos os caminhos possíveis no programa , incluindo caminhos que envolvem a escolha de métodos virtuais em tempo de execução . Esse problema pode ser arbitrariamente difícil ; pode envolver a execução simulada de milhões de caminhos de controle. A análise dos fluxos de controle local leva microssegundos e depende do tamanho do método. A análise dos fluxos de controle global pode levar horas, pois depende da complexidade de todos os métodos do programa e de todas as bibliotecas .
Então, por que não fazer uma análise mais barata que não precise analisar todo o programa e superestimar ainda mais severamente? Bem, proponha um algoritmo que funcione que não torne muito difícil escrever um programa correto que realmente seja compilado, e a equipe de design possa considerá-lo. Não conheço nenhum desses algoritmos.
Agora, o comentarista sugere "exigir que um construtor inicialize todos os campos". Isso não é uma má ideia. De fato, é uma idéia não tão ruim que C # já tenha esse recurso para estruturas . Um construtor struct é necessário para atribuir definitivamente todos os campos no momento em que o ctor retorna normalmente; o construtor padrão inicializa todos os campos com seus valores padrão.
E as aulas? Bem, como você sabe que um construtor inicializou um campo ? O ctor poderia chamar um método virtual para inicializar os campos e agora estamos de volta à mesma posição em que estávamos antes. Estruturas não têm classes derivadas; classes podem. É necessária uma biblioteca contendo uma classe abstrata para conter um construtor que inicialize todos os seus campos? Como a classe abstrata sabe a quais valores os campos devem ser inicializados?
John sugere simplesmente proibir métodos de chamada em um ctor antes que os campos sejam inicializados. Então, resumindo, nossas opções são:
A equipe de design escolheu a terceira opção.
fonte
bool x;
ser equivalente abool x = false;
mesmo dentro de um método ?Porque o compilador está tentando impedir que você cometa um erro.
A inicialização de sua variável
false
altera alguma coisa nesse caminho específico de execução? Provavelmente não, considerardefault(bool)
é falso, mas está forçando você a estar ciente de que isso está acontecendo. O ambiente .NET impede que você acesse "memória de lixo", pois inicializará qualquer valor para o padrão. Mas, ainda assim, imagine que esse era um tipo de referência e você passaria um valor não inicializado (nulo) para um método que espera um valor não nulo e obteria um NRE em tempo de execução. O compilador está simplesmente tentando impedir isso, aceitando o fato de que isso às vezes pode resultar embool b = false
instruções.Eric Lippert fala sobre isso em uma postagem no blog :
Por que isso não se aplica a um campo de classe? Bem, suponho que a linha tenha que ser desenhada em algum lugar, e a inicialização de variáveis locais é muito mais fácil de diagnosticar e acertar, em oposição aos campos de classe. O compilador pode fazer isso, mas pense em todas as verificações possíveis que precisam ser feitas (onde algumas são independentes do código da classe) para avaliar se cada campo de uma classe é inicializado. Não sou designer de compiladores, mas tenho certeza de que seria definitivamente mais difícil, pois há muitos casos que são levados em conta e devem ser feitos em tempo hábil . Para cada recurso que você precisa projetar, escrever, testar e implantar, o valor da implementação, em oposição ao esforço, não vale a pena e é complicado.
fonte
A resposta curta é que o código que acessa variáveis locais não inicializadas pode ser detectado pelo compilador de maneira confiável, usando análise estática. Considerando que este não é o caso dos campos. Portanto, o compilador aplica o primeiro caso, mas não o segundo.
Isso não passa de uma decisão de design da linguagem C #, conforme explicado por Eric Lippert . O CLR e o ambiente .NET não exigem isso. O VB.NET, por exemplo, compilará bem com variáveis locais não inicializadas e, na realidade, o CLR inicializa todas as variáveis não inicializadas para os valores padrão.
O mesmo poderia ocorrer com o C #, mas os designers de idiomas optaram por não. O motivo é que as variáveis inicializadas são uma fonte enorme de erros e, portanto, ao exigir a inicialização, o compilador ajuda a reduzir erros acidentais.
Então, por que essa inicialização explícita obrigatória não acontece com campos dentro de uma classe? Simplesmente porque essa inicialização explícita pode ocorrer durante a construção, por meio de uma propriedade chamada por um inicializador de objeto ou mesmo por um método chamado por muito tempo após o evento. O compilador não pode usar a análise estática para determinar se todos os caminhos possíveis através do código levam à variável inicial explicitamente antes de nós. Entender errado seria irritante, pois o desenvolvedor poderia ficar com um código válido que não será compilado. Portanto, o C # não o aplica e o CLR é deixado para inicializar automaticamente os campos com um valor padrão, se não definido explicitamente.
A imposição pelo C # da inicialização de variável local é limitada, o que geralmente chama a atenção dos desenvolvedores. Considere as quatro linhas de código a seguir:
A segunda linha de código não será compilada, pois está tentando ler uma variável de cadeia não inicializada. A quarta linha de código compila muito bem, como
array
foi inicializada, mas apenas com valores padrão. Como o valor padrão de uma string é nulo, obtemos uma exceção no tempo de execução. Qualquer pessoa que passou algum tempo aqui no Stack Overflow saberá que essa inconsistência explícita / implícita de inicialização leva a muitos "Por que estou recebendo um erro" Referência de objeto não definida para uma instância de um objeto "?" questões.fonte
public interface I1 { string str {get;set;} }
e um métodoint f(I1 value) { return value.str.Length; }
. Se isso existe em uma biblioteca, o compilador não pode saber a que biblioteca será vinculada, portanto, se aset
chamada foi chamada antes doget
campo O apoio não pode ser explicitamente inicializado, mas é necessário compilar esse código.f
. Seria gerado ao compilar os construtores. Se você deixar um construtor com um campo possivelmente não inicializado, isso seria um erro. Também pode haver restrições ao chamar métodos de classe e getters antes que todos os campos sejam inicializados.Boas respostas acima, mas pensei em publicar uma resposta muito mais simples / mais curta para as pessoas com preguiça de ler uma longa (como eu).
Classe
A propriedade
Boo
pode ou não ter sido inicializada no construtor. Portanto, quando descobrereturn Boo;
, não assume que foi inicializado. Simplesmente suprime o erro.Função
Os
{ }
caracteres definem o escopo de um bloco de código. O compilador percorre as ramificações desses{ }
blocos, acompanhando as coisas. É fácil dizer queBoo
não foi inicializado. O erro é acionado.Por que o erro existe?
O erro foi introduzido para reduzir o número de linhas de código necessárias para tornar o código fonte seguro. Sem o erro, o acima seria assim.
Do manual:
Referência: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
fonte