2010-07-13 21 views
9

¿Es mejor declarar una variable utilizada en un bucle fuera del bucle en lugar de dentro? A veces veo ejemplos en los que se declara una variable dentro del ciclo. ¿Esto efectivamente causa que el programa asigne memoria para una nueva variable cada vez que se ejecuta el ciclo? O es .NET lo suficientemente inteligente como para saber que en realidad es la misma variable.¿Las declaraciones de variables siempre deben colocarse fuera de un bucle?

Por ejemplo, vea el siguiente código desde this answer.

public static void CopyStream(Stream input, Stream output) 
{ 
    byte[] buffer = new byte[32768]; 
    while (true) 
    { 
     int read = input.Read (buffer, 0, buffer.Length); 
     if (read <= 0) 
      return; 
     output.Write (buffer, 0, read); 
    } 
} 

¿Sería esta versión modificada más eficiente?

public static void CopyStream(Stream input, Stream output) 
{ 
    int read; //OUTSIDE LOOP 
    byte[] buffer = new byte[32768]; 
    while (true) 
    { 
     read = input.Read (buffer, 0, buffer.Length); 
     if (read <= 0) 
      return; 
     output.Write (buffer, 0, read); 
    } 
} 

Respuesta

9

No, no sería más eficiente. Sin embargo, me gustaría volver a escribir de esta manera, que pasa a declararlo fuera del bucle de todos modos:

byte[] buffer = new byte[32768]; 
int read; 
while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 
{ 
    output.Write(buffer, 0, read); 
} 

No soy generalmente un ventilador de la utilización de efectos secundarios en las condiciones, pero con eficacia el método Read ustedes dos está dando bits de datos: si ha llegado al final de la transmisión o no, y cuánto ha leído. El ciclo while ahora dice: "Si bien hemos logrado leer algunos datos ... cópialos".

Es un poco como el uso de int.TryParse:

if (int.TryParse(text, out value)) 
{ 
    // Use value 
} 

vez que estés usando un efecto secundario de una llamada al método en el estado. Como digo, no hago un hábito de hacer esto excepto para este patrón en particular, cuando se trata de un método que devuelve dos bits de datos.

Lo mismo viene a leer las líneas de un TextReader:

string line; 
while ((line = reader.ReadLine()) != null) 
{ 
    ... 
} 

Para volver a la pregunta original: si una variable va a ser inicializado en cada iteración de un bucle y sólo se usa dentro del cuerpo del bucle, casi siempre lo declaro dentro del ciclo. Una pequeña excepción aquí es si la variable está siendo capturada por una función anónima; en ese punto hará una diferencia en el comportamiento, y elegiría la forma que me diera el comportamiento deseado ... pero eso es casi siempre el "declarar dentro" "formar de todos modos.

EDITAR: Cuando se trata de determinar el alcance, el código de arriba deja la variable en un ámbito mayor de lo que necesita ... pero creo que lo hace más claro. Siempre se puede tratar mediante la introducción de un nuevo ámbito si es que quiere:

{ 
    int read; 
    while (...) 
    { 
    } 
} 
+1

Estoy de acuerdo con el tema de alcance. Cuando alguien tiene que leer el código anterior (o el código de otra persona), es bueno tener variables con el alcance adecuado. Cuando salgo del alcance, ya no tengo que preocuparme por cuál será el último valor de esa variable. – Torlack

+0

@Torlack: voy a editar para abordar esto. –

1

general, he preferido este último como una cuestión de hábito personal, porque, incluso si es lo suficientemente inteligente .NET, otros entornos en los que me podría funcionar más tarde puede no ser lo suficientemente inteligente. No podría ser nada más que compilar una línea adicional de código dentro del ciclo para reiniciar la variable, pero aún está sobrecargada.

Incluso si son idénticos para todos los propósitos mensurables en cualquier ejemplo dado, diría que este último tiene menos posibilidades de causar problemas a largo plazo.

+2

No estoy de acuerdo, porque ahora tiene una variable con un alcance mayor de lo que necesita, lo que generalmente es malo para la legibilidad. Personalmente, creo que debes adaptarte a las expresiones idiomáticas del entorno en el que trabajas: si tratas de codificar C++, Java y C# de la misma manera, terminarás teniendo problemas más grandes que este. –

+0

Bastante, y definitivamente estoy de acuerdo en que uno debe usar las herramientas correctamente en lugar de intentar forzarlas a parecerse. Ciertamente no querría insinuar lo contrario. Supongo que en este ejemplo particular, el alcance no es un gran problema porque de todos modos termina inmediatamente después. Definitivamente hay mucho que decir sobre el contexto del código en su conjunto, ya que rara vez hay una solución global para todas las instancias de problemas similares. – David

3

En el entorno poco probable que no le ayuda con esto, todavía sería un micro-optimización. Factores como la claridad y el alcance adecuado son mucho más importantes que el caso extremo en el que esto podría ser casi nulo.

Debe dar a sus variables el alcance adecuado sin pensar en el rendimiento. Por supuesto, las inicializaciones complejas son una bestia diferente, por lo que si algo solo se inicializara una vez, pero solo se utilizara dentro de un bucle, igual desearía declararlo afuera.

+0

+1 Incluso si la variable se asigna con cada iteración del ciclo, probablemente quieras declararlo dentro. La diferencia de rendimiento es, en la mayoría de los casos, insignificante, pero el alcance no lo es. – helpermethod

+0

Estoy de acuerdo, estaba hablando principalmente de variables que se usan pero no se modifican dentro del ciclo, pero se declaran e inicializan fuera del ciclo porque la inicialización del objeto no es trivial.Como Jon Skeet mencionó en su respuesta, puede introducir un nuevo alcance para mantenerlo fuera del ciclo pero aún con un alcance adecuado. Esto funciona muy bien con cosas como identificadores de recursos, en cuyo caso puede introducir un nuevo alcance con el uso de (...) constructo. –

2

Voy a estar de acuerdo con la mayoría de estas otras respuestas con una advertencia.

Si está utilizando expresiones lambada, debe tener cuidado con la captura de variables.

static void Main(string[] args) 
{ 
    var a = Enumerable.Range(1, 3); 
    var b = a.GetEnumerator(); 
    int x; 
    while(b.MoveNext()) 
    { 
     x = b.Current; 
     Task.Factory.StartNew(() => Console.WriteLine(x)); 
    } 
    Console.ReadLine(); 
} 

dará el resultado

3 
3 
3 

Dónde

static void Main(string[] args) 
{ 
    var a = Enumerable.Range(1, 3); 
    var b = a.GetEnumerator(); 
    while(b.MoveNext()) 
    { 
     int x = b.Current; 
     Task.Factory.StartNew(() => Console.WriteLine(x)); 
    } 
    Console.ReadLine(); 
} 

dará el resultado

1 
2 
3 

o algún orden allí de. Esto se debe a que cuando la tarea finalmente se inicia verificará el valor actual de su referencia a x. en el primer ejemplo, los 3 bucles apuntaban a la misma referencia, donde en el segundo ejemplo apuntaban a diferentes referencias.

2

Como es el caso con muchas optimizaciones simples como esta, el compilador se encarga de ello por usted. Si intenta ambos y mire IL las asambleas en ildasm se puede ver que ambos declaran un único int32 leen variables, aunque no reordenar las declaraciones:

.locals init ([0] int32 read, 
      [1] uint8[] buffer, 
      [2] bool CS$4$0000) 

    .locals init ([0] uint8[] buffer, 
      [1] int32 read, 
      [2] bool CS$4$0000) 
Cuestiones relacionadas