Por que posso declarar uma variável filho com o mesmo nome que uma variável no escopo pai?

23

Escrevi recentemente algum código em que reutilizei involuntariamente um nome de variável como parâmetro de uma ação declarada em uma função que já possui uma variável com o mesmo nome. Por exemplo:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

Quando vi a duplicação, fiquei surpreso ao ver que o código compilava e executava perfeitamente, o que não é um comportamento que eu esperaria com base no que sei sobre o escopo em C #. Uma rápida Googling transformou-se tão perguntas que se queixam de que um código semelhante se produzir um erro, como o Lambda Âmbito Esclarecimento . (Colei esse código de exemplo no meu IDE para ver se ele seria executado, apenas para garantir; ele funciona perfeitamente.) Além disso, quando eu insiro a caixa de diálogo Renomear no Visual Studio, o primeiro xé destacado como um conflito de nome.

Por que esse código funciona? Estou usando o C # 8 com o Visual Studio 2019.

stellr42
fonte
11
O lambda é movido para um método em uma classe gerada pelo compilador e, portanto, todo o xparâmetro desse método é movido para fora do escopo. Veja sharplab para um exemplo.
Lasse V. Karlsen
6
Provavelmente, vale a pena notar aqui que isso não será compilado ao segmentar o C # 7.3, portanto, isso parece ser exclusivo do C # 8.
Jonathon Chase
O código na pergunta vinculada também compila bem no sharplab . Esta pode ser uma mudança recente.
Lasse V. Karlsen
2
encontrou um dupe (sem resposta): stackoverflow.com/questions/58639477/…
bolov 29/01

Respostas:

26

Por que esse código funciona? Estou usando o C # 8 com o Visual Studio 2019.

Você respondeu sua própria pergunta! É porque você está usando C # 8.

A regra de C # 1 a 7 era: um nome simples não pode ser usado para significar duas coisas diferentes no mesmo escopo local. (A regra real era um pouco mais complexa que essa, mas descreve como é tedioso; consulte a especificação do C # para obter detalhes.)

A intenção dessa regra era impedir o tipo de situação que você está falando no seu exemplo, onde fica muito fácil confundir o significado do local. Em particular, essa regra foi projetada para evitar confusões como:

class C 
{
  int x;
  void M()
  {
    x = 123;
    if (whatever)
    {
      int x = 356;
      ...

E agora temos uma situação em que, dentro do corpo de M, xsignifica ambos this.xe o local x.

Embora bem-intencionado, houve vários problemas com essa regra:

  • Não foi implementado para especificação. Havia situações em que um nome simples podia ser usado como, digamos, um tipo e uma propriedade, mas nem sempre eram sinalizados como erros porque a lógica de detecção de erros era falha. (Ver abaixo)
  • As mensagens de erro foram redigidas de maneira confusa e relatadas de maneira inconsistente. Havia várias mensagens de erro diferentes para essa situação. Eles identificaram inconsistentemente o agressor; isto é, às vezes o uso interno seria destacado, às vezes o externo , e às vezes era apenas confuso.

Fiz um esforço na reescrita de Roslyn para resolver isso; Eu adicionei algumas novas mensagens de erro e tornei as antigas consistentes em relação a onde o erro foi relatado. No entanto, esse esforço foi muito pouco, muito tarde.

A equipe do C # decidiu para o C # 8 que toda a regra estava causando mais confusão do que impedia, e a regra foi retirada do idioma. (Obrigado Jonathon Chase por determinar quando a aposentadoria aconteceu.)

Se você estiver interessado em aprender o histórico desse problema e como tentei corrigi-lo, consulte estes artigos que escrevi sobre ele:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

No final da parte três, observei que havia também uma interação entre esse recurso e o recurso "Color Color" - ou seja, o recurso que permite:

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

Aqui usamos o nome simples Colorpara se referir a ambos this.Colore ao tipo enumerado Color; de acordo com uma leitura estrita da especificação, isso deve ser um erro, mas, neste caso, a especificação estava errada e a intenção era permiti-la, pois esse código é inequívoco e seria irritante fazer o desenvolvedor alterá-lo.

Eu nunca escrevi esse artigo descrevendo todas as interações estranhas entre essas duas regras, e seria um pouco inútil fazê-lo agora!

Eric Lippert
fonte
O código na pergunta falha ao compilar para C # 6, 7, 7.1, 7.2 e 7.3, fornecendo "CS0136: Um local ou parâmetro chamado 'x' não pode ser declarado neste escopo porque esse nome ...". Parece que a regra ainda é aplicada até C # 8.
Jonathon Chase
@ JonathonChase: Obrigado!
Eric Lippert