¿La variable declarada en for-loop es una variable local?

133

He estado usando C # durante bastante tiempo, pero nunca me di cuenta de lo siguiente:

 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
 }

Entonces, ¿por qué no puedo usar el valor de 'i' fuera del bloque for si no me permite declarar una variable con este nombre?

Pensé que la variable iteradora utilizada por un bucle for solo es válida en su alcance.

Juan v
fuente
8
Debido a que el alcance del bloque externo incluye el alcance del bucle for
V4Vendetta
3
Una forma en que lo pienso (y algunas guías de estilo de código requieren esto, especialmente para los lenguajes de tipo dinámico) es que todas las variables declaradas en un ámbito podrían declararse al comienzo de ese ámbito, lo que significa que su función podría reescribirseint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith
2
@ V4Vendetta: es más al revés. El bloque interno es un cuadro negro del bloque principal.
Sebastian Mach el
44
Además de las razones técnicas por las cuales esto no es posible, ¿por qué esto (o una variante de esto) sería algo sensato? Obviamente tiene un propósito diferente, así que dale un nombre diferente.
Dave
No me di cuenta de esto en absoluto, pero parece una restricción completamente tonta.
alan2here

Respuestas:

119

La razón por la que no se le permite definir una variable con el mismo nombre tanto en el bucle for como fuera del bucle for es porque las variables en el alcance externo son válidas en el alcance interno. Lo que significa que habría dos variables 'i' dentro del ciclo for si se permitiera.

Ver: alcances de MSDN

Específicamente:

El alcance de una variable local declarada en una declaración de variable local (Sección 8.5.1) es el bloque en el que se produce la declaración.

y

El alcance de una variable local declarada en un inicializador for de una instrucción for (Sección 8.8.3) es el inicializador for, la condición for, el iterador for y la instrucción contenida de la instrucción for.

Y también: Declaraciones de variables locales (Sección 8.5.1 de la especificación C #)

Específicamente:

El alcance de una variable local declarada en una declaración de variable local es el bloque en el que se produce la declaración. Es un error hacer referencia a una variable local en una posición textual que precede al declarador de variable local de la variable local. Dentro del alcance de una variable local, es un error en tiempo de compilación declarar otra variable local o constante con el mismo nombre.

(El énfasis es mío).

Lo que significa que el alcance del iinterior de su ciclo for es el ciclo for. Mientras que el alcance del iexterior de su ciclo for es el método principal completo más el ciclo for. Lo que significa que tendría dos ocurrencias identro del bucle que no es válido de acuerdo con lo anterior.

La razón por la que no se le permite hacer int A = i;es porque int isolo tiene un alcance para su uso dentro del forciclo. Por lo tanto, ya no es accesible fuera del forbucle.

Como puede ver, estos dos problemas son el resultado del alcance; El primer problema ( int i = 4;) daría como resultado dos ivariables dentro del foralcance del bucle. Mientras que int A = i;daría como resultado el acceso a una variable que está fuera del alcance.

Lo que podría hacer en su lugar es declarar el ialcance de todo el método y luego usarlo tanto en el método como en el ámbito del bucle for. Esto evitará romper cualquiera de las reglas.

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

EDITAR :

Por supuesto, el compilador de C # podría modificarse para permitir que este código se compile de manera bastante válida. Después de todo esto es válido:

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

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

Pero, ¿sería realmente beneficioso para la legibilidad y facilidad de mantenimiento de su código poder escribir 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);
}

Piense en el potencial de errores aquí, ¿el último iimprime 0 o 4? Ahora, este es un ejemplo muy pequeño, uno que es bastante fácil de seguir y rastrear, pero definitivamente es mucho menos fácil de mantener y leer que haber declarado el exterior icon un nombre diferente.

NÓTESE BIEN:

Tenga en cuenta que las reglas de alcance de C # difieren de las reglas de alcance de C ++ . En C ++, las variables solo están dentro del alcance desde donde se declaran hasta el final del bloque. Lo que haría que su código sea una construcción válida en C ++.

Johannes Kommer
fuente
Tiene sentido, aunque creo que debería equivocarse en la ivariable interna ; eso me parece más obvio.
George Duckett
¿Pero el alcance es para el comando y su {}? ¿O significa su padre {}?
John V
2
Bueno, si declaro 'A' DESPUÉS de la declaración for, no es válida en el ciclo for ya que se declara más tarde. Es por eso que no entiendo por qué no se puede usar el mismo nombre.
John V el
9
Quizás esta respuesta debería, para completar, señalar que las reglas de alcance de C # son diferentes de las de C ++ en este caso. En C ++, las variables solo están dentro del alcance desde donde se declaran hasta el final del bloque (consulte msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT
2
Tenga en cuenta que Java adopta un enfoque intermedio entre C ++ y C #: como es el ejemplo del OP, sería válido en Java, pero si la idefinición externa se moviera antes del ciclo for, la idefinición interna se marcaría como no válida.
Nicola Musatti el
29

La respuesta de J.Kommer es correcta: brevemente, es ilegal declarar una variable local en un espacio de declaración de variable local que se superpone a otro espacio de declaración de variable local que tiene un local del mismo nombre.

Hay una regla adicional de C # que también se viola aquí. La regla adicional es que es ilegal usar un nombre simple para referirse a dos entidades diferentes dentro de dos espacios de declaración de variables locales superpuestos diferentes. Entonces, no solo su ejemplo es ilegal, también es ilegal:

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

Porque ahora el nombre simple "x" se ha utilizado dentro del espacio de declaración de variable local de "y" para significar dos cosas diferentes: "this.x" y la "x" local.

Consulte http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ para obtener más análisis de estos problemas.

Eric Lippert
fuente
2
Además, es interesante observar que su código de ejemplo se compilará cuando realice el cambioint y = this.x;
Phil
44
@ Phil: Correcto. this.xNo es un nombre simple .
Eric Lippert el
13

Hay una forma de declarar y usar identro del método después del ciclo:

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

    }

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

Puede hacer esto en Java (podría originarse en C, no estoy seguro). Por supuesto, es un poco desordenado por el bien de un nombre de variable.

Chris S
fuente
Sí, esto se origina en C / C ++.
Branko Dimitrijevic el
1
No sabía esto antes. Característica de lenguaje ordenado!
@MathiasLykkegaardLorenzen sí
Chris S
7

Si ha declarado i antes de su forciclo, ¿cree que aún debería ser válido declararlo dentro del ciclo?

No, porque entonces el alcance de los dos se superpondría.

En cuanto a no poder hacerlo int A=i;, bueno, eso es simplemente porque isolo existe en el forbucle, como debería ser.

Widor
fuente
7

Además de la respuesta de J.Kommer (+1 por cierto). Hay esto en el estándar para el alcance NET:

bloque Si declara una variable dentro de una construcción de bloque, como una instrucción If, el alcance de esa variable es solo hasta el final del bloque. La vida útil es hasta que finalice el procedimiento.

Procedimiento Si declara una variable dentro de un procedimiento, pero fuera de cualquier instrucción If, el alcance es hasta la función End Sub o End. La vida útil de la variable es hasta que finalicen los procedimientos.

Por lo tanto, el int i decalared dentro del encabezado del bucle for solo estará dentro del alcance durante el bloque del bucle for, PERO su vida útil dura hasta Main()que se complete el código.

ChrisBD
fuente
5

La forma más fácil de pensar en esto es mover la declaración externa de I por encima del bucle. Debería ser obvio entonces.

Es el mismo alcance de cualquier manera, por lo tanto, no se puede hacer.

Andrew Barber
fuente
4

Además, las reglas de C # muchas veces no son necesarias en términos de programación estricta, pero están ahí para mantener su código limpio y legible.

por ejemplo, podrían haberlo hecho de modo que si lo define después del ciclo, entonces está bien, sin embargo, si alguien lee su código y omite la línea de definición, puede pensar que tiene que ver con la variable del ciclo.

Chico
fuente
2

La respuesta de Kommer es técnicamente correcta. Permítanme parafrasearlo con una vívida metáfora de pantalla ciega.

Hay una pantalla ciega unidireccional entre el bloque for y el bloque exterior adjunto, de modo que el código dentro del bloque for puede ver el código externo pero el código en el bloque externo no puede ver el código dentro.

Como el código externo no puede ver el interior, no puede usar nada declarado dentro. Pero dado que el código en el bloque for puede ver tanto el interior como el exterior, una variable declarada en ambos lugares no se puede utilizar de forma inequívoca por su nombre.

Entonces, ¡o no lo ves, o eres C #!

explorador
fuente
0

Míralo de la misma manera que si pudieras declarar un inten un usingbloque:

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

Sin embargo, dado intque no se implementa IDisposable, esto no se puede hacer. Sin intembargo, puede ayudar a alguien a visualizar cómo se coloca una variable en un ámbito privado.

Otra forma sería decir:

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

Espero que esto ayude a visualizar lo que está sucediendo.

Realmente me gusta esta característica, ya que declarar intdesde el interior del forbucle mantiene el código agradable y ajustado.

jp2code
fuente
WTF? Si va a etiquetar un NEG, tenga las bolas para decir por qué.
jp2code el
No es el votante negativo y creo que la comparación con usingestá bastante bien, aunque la semántica es bastante diferente (por lo que tal vez se rechazó). Tenga en cuenta que if (true)es redundante. Quítelo y tendrá un bloque de alcance.
Abel