Variável declarada no loop for é variável local?


133

Uso o C # há bastante tempo, mas nunca percebi o seguinte:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

Então, por que não posso usar o valor de 'i' fora do bloco for se ele não me permite declarar uma variável com esse nome?

Eu pensei que a variável iteradora usada por um loop for é válida apenas em seu escopo.


8
Uma vez que o âmbito bloco externo inclui o âmbito do loop for
V4Vendetta

3
Uma maneira de pensar nisso (e alguns guias de estilo de código exigem isso, especialmente para linguagens de tipo dinâmico) é que todas as variáveis ​​declaradas em um escopo podem ser declaradas no início desse escopo, o que significa que sua função pode ser reescritaint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith

2
@ V4Vendetta: É mais o contrário. O bloco interno é uma caixa preta para o bloco pai.
Sebastian Mach

4
Além das razões técnicas pelas quais isso não é possível, por que isso (ou uma variante disso) seria algo sensato a se fazer ?! Obviamente, serve a um propósito diferente, portanto, dê-lhe um nome diferente.
Dave

Eu não percebi isso, mas parece uma restrição completamente idiota.
Alan2here 3/11

Respostas:


119

O motivo pelo qual você não pode definir uma variável com o mesmo nome no loop for e fora do loop for é porque as variáveis ​​no escopo externo são válidas no escopo interno. Significando que haveria duas variáveis ​​'i' dentro do loop for, se isso fosse permitido.

Consulte: Escopos do MSDN

Especificamente:

O escopo de uma variável local declarada em uma declaração de variável local (Seção 8.5.1) é o bloco em que a declaração ocorre.

e

O escopo de uma variável local declarada no inicializador for de uma instrução for (Seção 8.8.3) é o inicializador, a condição for, o iterador e a instrução contida na instrução for.

E também: Declarações de variáveis ​​locais (Seção 8.5.1 da especificação C #)

Especificamente:

O escopo de uma variável local declarada em uma declaração de variável local é o bloco em que a declaração ocorre. É um erro referir-se a uma variável local em uma posição textual que precede o declarador de variável local da variável local. No escopo de uma variável local, é um erro em tempo de compilação declarar outra variável ou constante local com o mesmo nome.

(Ênfase minha.)

O que significa que o escopo do iinterior do loop for é o loop for. Considerando que o escopo da parte iexterna do loop for é todo o método principal mais o loop for. Ou seja, você teria duas ocorrências identro do loop que são inválidas de acordo com o acima.

A razão pela qual você não pode fazer isso int A = i;é porque o int iescopo é apenas para uso dentro do forloop. Portanto, não é mais acessível fora do forloop.

Como você pode ver, esses dois problemas são resultado do escopo; o primeiro problema ( int i = 4;) resultaria em duas ivariáveis ​​no forescopo do loop. Considerando int A = i;que resultaria no acesso a uma variável que está fora do escopo.

Em vez disso, o que você pode fazer é declarar o iescopo definido para todo o método e usá-lo no método e no escopo do loop for. Isso evitará violar qualquer regra.

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

EDIT :

Obviamente, o compilador C # pode ser alterado para permitir que esse código seja compilado com bastante validade. Depois de tudo isso é válido:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

Mas seria realmente benéfico para a legibilidade e a manutenção do código poder escrever código como:

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Pense no potencial de erros aqui, o último iimprime 0 ou 4? Agora, este é um exemplo muito pequeno, fácil de seguir e rastrear, mas é definitivamente muito menos sustentável e legível do que ter declarado o exterior icom um nome diferente.

NB:

Observe que as regras de escopo do C # diferem das regras de escopo do C ++ . Em C ++, as variáveis ​​estão apenas no escopo de onde são declaradas até o final do bloco. O que tornaria seu código uma construção válida em C ++.


Faz sentido, eu acho que deve erro no interior ivariável embora; isso parece mais óbvio para mim.
George Duckett

Mas o escopo é para comando e seu {}? Ou significa seu pai {}?
João V

2
Bem, se eu declarar 'A' APÓS a instrução for, ela não é válida no loop for, pois é declarada posteriormente. É por isso que não entendo por que o mesmo nome não pode ser usado.
João V

9
Talvez essa resposta deva, por completo, apontar que as regras de escopo do C # são diferentes daquelas do C ++ nesse caso. No C ++, as variáveis ​​estão no escopo apenas de onde são declaradas até o final do bloco (consulte msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT

2
Observe que Java adota uma abordagem intermediária entre C ++ e C #: como o exemplo do OP seria válido em Java, mas se a idefinição externa fosse movida antes do loop for, a idefinição interna seria marcada como inválida.
Nicola Musatti

29

A resposta de J.Kommer está correta: brevemente, é ilegal que uma variável local seja declarada em um espaço de declaração de variável local que se sobreponha a outro espaço de declaração de variável local que tenha um local com o mesmo nome.

Há uma regra adicional de C # que é violada aqui também. A regra adicional é que é ilegal que um nome simples seja usado para se referir a duas entidades diferentes dentro de dois espaços de declaração de variáveis ​​locais sobrepostos diferentes. Portanto, o seu exemplo não é apenas ilegal, mas também é ilegal:

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

Como agora o nome simples "x" foi usado dentro do espaço de declaração da variável local "y" para significar duas coisas diferentes - "this.x" e o local "x".

Consulte http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ para obter mais análises sobre esses problemas.


2
Além disso, é interessante notar que o código de exemplo irá compilar quando você fazer a mudançaint y = this.x;
Phil

4
@ Phil: Correto. this.xnão é um nome simples .
precisa

13

Existe uma maneira de declarar e usar identro do método após o loop:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Você pode fazer isso em Java (pode ser originário de C, não tenho certeza). É claro que é um pouco confuso por causa de um nome de variável.


Sim, isso se origina do C / C ++.
Branko Dimitrijevic

1
Eu não sabia disso antes. Recurso de linguagem elegante!

@MathiasLykkegaardLorenzen yes
Chris S

7

Se você declarou i antes do seu forloop, você acha que ainda deve ser válido declará-lo dentro do loop?

Não, porque o escopo dos dois se sobreporia.

Quanto a não ser capaz de fazer int A=i;, bem, isso é simplesmente porque isó existe no forloop, como deveria.


7

Além da resposta de J.Kommer (+1 btw). Existe isso no escopo padrão do NET:

block Se você declarar uma variável em uma construção de bloco, como uma instrução If, o escopo dessa variável será apenas até o final do bloco. A vida útil é até o procedimento terminar.

Procedimento Se você declarar uma variável dentro de um procedimento, mas fora de qualquer instrução If, o escopo será até a End Sub ou End Function. O tempo de vida da variável é até o término dos procedimentos.

Portanto, o int i decalarado dentro do cabeçalho do loop for estará no escopo apenas durante o bloco do loop for, MAS a vida útil dura até o Main()código ser concluído.


5

A maneira mais fácil de pensar sobre isso é mover a declaração externa de I para acima do loop. Deve ficar óbvio então.

É o mesmo escopo de qualquer maneira, portanto, não pode ser feito.


4

Além disso, as regras do C # muitas vezes não são necessárias em termos de programação estritamente, mas existem para manter seu código limpo e legível.

por exemplo, eles poderiam ter feito isso para que, se você o definir após o loop, tudo bem, mas alguém que lê seu código e perdeu a linha de definição pode pensar que isso tem a ver com a variável do loop.


2

A resposta de Kommer é tecnicamente correta. Deixe-me parafrasear com uma vívida metáfora da tela cega.

Existe uma tela cega unidirecional entre o bloco for e o bloco externo anexo, de forma que o código de dentro do bloco for possa ver o código externo, mas o código no bloco externo não pode ver o código dentro.

Como o código externo não pode ver o interior, ele não pode usar nada declarado no interior. Mas como o código no bloco for pode ver dentro e fora, uma variável declarada nos dois lugares não pode ser usada sem ambiguidade pelo nome.

Então, ou você não vê, ou você C #!


0

Veja da mesma maneira como se você pudesse declarar um intem um usingbloco:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

No entanto, como intnão implementa IDisposable, isso não pode ser feito. No intentanto, pode ajudar alguém a visualizar como uma variável é colocada em um escopo privado.

Outra maneira seria dizer,

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

Espero que isso ajude a visualizar o que está acontecendo.

Eu realmente gosto desse recurso, pois declarar o intde dentro do forloop mantém o código agradável e rígido.


WTF? Se você quiser marcar um NEG, tenha coragem de dizer o porquê.
Jp2code # 1

Não é o downvoter e acho que a comparação com ele usingé bastante boa, embora a semântica seja bastante diferente (e talvez por isso foi que o voto foi negativo). Observe que if (true)é redundante. Remova-o e você terá um bloco de escopo.
Abel
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.