Percebi algum comportamento bizarro no meu código ao comentar acidentalmente uma linha em uma função durante a revisão do código. Foi muito difícil de reproduzir, mas mostrarei um exemplo semelhante aqui.
Eu tenho essa classe de teste:
public class Test
{
public void GetOut(out EmailAddress email)
{
try
{
Foo(email);
}
catch
{
}
}
public void Foo(EmailAddress email)
{
}
}
não há nenhuma atribuição ao e-mail na GetOut
qual normalmente geraria um erro:
O parâmetro de saída 'email' deve ser atribuído antes que o controle deixe o método atual
No entanto, se EmailAddress estiver em uma estrutura em um assembly separado, não haverá erro criado e tudo será compilado corretamente.
public struct EmailAddress
{
#region Constructors
public EmailAddress(string email)
: this(email, string.Empty)
{
}
public EmailAddress(string email, string name)
{
this.Email = email;
this.Name = name;
}
#endregion
#region Properties
public string Email { get; private set; }
public string Name { get; private set; }
#endregion
}
Por que o compilador não impõe que o E-mail deva ser atribuído? Por que esse código é compilado se a estrutura é criada em um assembly separado, mas não é compilado se a estrutura é definida no assembly existente?
struct Dog{}
, tudo está bem.Respostas:
TLDR: Este é um bug conhecido de longa data. Eu escrevi sobre isso em 2010:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/
É inofensivo e você pode ignorá-lo com segurança e parabenizar-se por encontrar um bug um tanto obscuro.
Ah, sim, de certa forma. Apenas tem uma idéia errada de que condição implica que a variável seja definitivamente atribuída, como veremos.
Esse é o ponto crucial do bug. O bug é uma consequência da interseção de como o compilador C # faz a verificação definitiva da atribuição nas estruturas e como o compilador carrega metadados das bibliotecas.
Considere isto:
OK, neste ponto, o que sabemos?
f
é um alias para uma variável do tipoFoo
, portanto, o armazenamento já foi alocado e é definitivamente pelo menos no estado em que saiu do alocador de armazenamento. Se houver um valor colocado na variável pelo chamador, esse valor estará lá.O que precisamos? Exigimos que
f
seja definitivamente atribuído em qualquer ponto em que o controle saiaM
normalmente. Então, você esperaria algo como:que define
f.x
ef.y
com seus valores padrão. Mas e isso?Isso também deve estar bem. Mas, e aqui está o kicker, por que precisamos atribuir os valores padrão apenas para impressioná-los um momento depois? O verificador de atribuição definitiva do C # verifica se todos os campos estão atribuídos! Isso é legal:
E por que isso não deveria ser legal? É um tipo de valor.
f
é uma variável e já contém um valor válido do tipoFoo
, então vamos definir os campos e pronto, certo?Direita. Então, qual é o erro?
O bug que você descobriu é: como uma economia de custos, o compilador C # não carrega os metadados para campos particulares de estruturas que estão nas bibliotecas referenciadas . Esses metadados podem ser enormes e reduziriam a velocidade do compilador para pouquíssimas vitórias para carregar tudo na memória todas as vezes.
E agora você deve poder deduzir a causa do bug que encontrou. Quando o compilador verifica se o parâmetro out está definitivamente atribuído, ele compara o número de campos conhecidos com o número de campos definidos inicialmente e, no seu caso, ele conhece apenas os zero campos públicos porque os metadados do campo privado não foram carregados. . O compilador conclui "zero campos obrigatórios, zero campos inicializados, estamos bem".
Como eu disse, esse bug existe há mais de uma década e pessoas como você ocasionalmente o redescobrem e denunciam. É inofensivo e é improvável que seja consertado, pois consertá-lo tem quase zero benefício, mas um grande custo de desempenho.
E é claro que o bug não se repete para campos privados de estruturas que estão no código-fonte no seu projeto, porque obviamente o compilador já tem informações sobre os campos privados em mãos.
fonte
System.TimeSpan
, os erros vêm:error CS0269: Use of unassigned out parameter 'email'
eerror CS0177: The out parameter 'email' must be assigned to before control leaves the current method
. Existe apenas um campo não estático deTimeSpan
, a saber_ticks
. Éinternal
para sua montagem mscorlib. Esta montagem é especial? Mesmo comSystem.DateTime
, e seu campo éprivate
Embora pareça um bug, faz algum sentido.
O 'erro ausente' aparece apenas ao usar uma biblioteca de classes. E uma biblioteca de classes pode ter sido escrita em outra linguagem .net, por exemplo, VB.Net. O 'rastreamento de atribuição definida' é um recurso do C #, não da estrutura.
Portanto, no geral, não acho que seja um bug, mas não conheço uma declaração oficial para isso.
fonte
default(T)
). Portanto, não há violação da segurança da memória ou algo semelhante.