2010-07-13 16 views
9

È meglio dichiarare una variabile utilizzata in un ciclo al di fuori del ciclo piuttosto che all'interno? A volte vedo esempi in cui una variabile viene dichiarata all'interno del ciclo. Ciò causa effettivamente il programma per allocare memoria per una nuova variabile ogni volta che viene eseguito il ciclo? Oppure .NET è abbastanza intelligente da sapere che è davvero la stessa variabile.Le dichiarazioni delle variabili dovrebbero sempre essere posizionate al di fuori di un ciclo?

Ad esempio, vedere il codice seguente da 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); 
    } 
} 

Questa versione modificata potrebbe essere più efficace?

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

risposta

9

No, non sarebbe più efficiente. Tuttavia, mi piacerebbe riscrivere in questo modo che avviene di dichiararla fuori del ciclo in ogni caso:

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

Io non sono in genere un fan di utilizzare effetti collaterali in condizioni, ma in modo efficace il metodo Read sta dando voi due bit di dati: se hai raggiunto o meno la fine dello stream e quanto hai letto. Il ciclo while ora sta dicendo: "Mentre siamo riusciti a leggere alcuni dati ... copialo".

E 'un po' come con int.TryParse:

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

Anche in questo caso si utilizza un effetto collaterale della chiamata al metodo nella condizione. Come ho detto, non prendo l'abitudine di fare questo eccetto per questo particolare modello, quando hai a che fare con un metodo che restituisce due bit di dati.

La stessa cosa viene in sulla lettura di righe da un TextReader:

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

Per tornare alla tua domanda iniziale: se una variabile sta per essere inizializzato in ogni iterazione di un ciclo e è usata solo all'interno del corpo del loop, lo dichiarerei quasi sempre all'interno del loop. Una piccola eccezione qui è se la variabile viene catturata da una funzione anonima - a quel punto farà una differenza di comportamento, e sceglierei quale forma mi ha dato il comportamento desiderato ... ma questo è quasi sempre il "dichiarare dentro" "formi comunque.

EDIT: Quando si tratta di scoping, il codice sopra lascia effettivamente la variabile in un ambito più ampio di quello che deve essere ... ma credo che renda il ciclo più chiaro. È sempre possibile affrontare questo con l'introduzione di un nuovo ambito se si cura di:

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

Sono d'accordo con la questione portata. Quando qualcuno deve leggere il vecchio codice (o il codice di qualcun altro), è bello avere una variabile con un ambito appropriato. Quando esco dall'ambito, non devo più preoccuparmi di quale potrebbe essere l'ultimo valore di tale variabile. – Torlack

+0

@Torlack: modificherò per risolvere questo problema. –

1

ho generalmente preferito quest'ultima come una questione di abitudine personale, perché, anche se .NET è abbastanza intelligente, altri ambienti in cui ho potrebbe funzionare più tardi potrebbe non essere abbastanza intelligente. Potrebbe non essere altro che compilare una riga di codice aggiuntiva all'interno del ciclo per reinizializzare la variabile, ma è ancora sovraccarico.

Anche se sono identici per tutti gli scopi misurabili in un dato esempio, direi che quest'ultimo ha meno possibilità di causare problemi a lungo termine.

+2

Non sono d'accordo - perché ora hai una variabile con un ambito più ampio del necessario, che in genere è negativo per la leggibilità. Personalmente penso che dovresti adattarti agli idiomi dell'ambiente in cui lavori - se provi a codificare C++, Java e C# allo stesso modo, finirai con problemi più grandi di questo. –

+0

Abbastanza corretto, e sono assolutamente d'accordo sul fatto che uno dovrebbe usare gli strumenti correttamente piuttosto che cercare di forzarli a somigliarsi. Di certo non vorrei sottintendere diversamente. Immagino che in questo particolare esempio lo scope non sia un grosso problema perché termina immediatamente dopo comunque. C'è sicuramente molto da dire per il contesto del codice nel suo complesso, poiché raramente c'è una soluzione globale per tutte le istanze di problemi simili. – David

1

Non importa, e se stavo rivedendo il codice per quel particolare esempio, non mi interesserebbe in alcun modo.

Tuttavia, si tenga presente che i due possono significare cose molto diverse se si finisce per acquisire la variabile "leggi" in una chiusura.

Vedi questo ottimo post di Eric Lippert in cui questo problema viene in su per quanto riguarda i cicli foreach - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

3

Nell'ambiente improbabile che non ti aiuta con questo, sarebbe comunque un micro-ottimizzazione. Fattori come la chiarezza e la scoping corretta sono molto più importanti del caso limite in cui questo potrebbe solo fare quasi nessuna differenza.

Si dovrebbe dare alle variabili l'ambito corretto senza pensare alle prestazioni. Naturalmente, le inizializzazioni complesse sono una bestia diversa, quindi se qualcosa deve essere inizializzato solo una volta, ma è usato solo all'interno di un ciclo, lo si vorrebbe comunque dichiararlo all'esterno.

+0

+1 Anche se la variabile è allocata ad ogni iterazione del ciclo, probabilmente vorrai dichiararla all'interno. La differenza di prestazioni è per la maggior parte del tempo trascurabile, ma l'ambito non lo è. – helpermethod

+0

Sono d'accordo, stavo parlando principalmente di variabili che sono usate ma non modificate all'interno del ciclo, ma dichiarate e inizializzate al di fuori del ciclo, perché l'inizializzazione dell'oggetto non è banale.Come Jon Skeet ha menzionato nella sua risposta, è possibile introdurre un nuovo ambito per mantenerlo al di fuori del ciclo ma con una scoping adeguata. Funziona molto bene con cose come gli handle delle risorse, nel qual caso puoi introdurre un nuovo scope con il costrutto using (...). –

2

Sono d'accordo con la maggior parte di queste altre risposte con un avvertimento.

Se si utilizzano le espressioni lambada, è necessario prestare attenzione alle variabili di acquisizione.

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à il risultato

3 
3 
3 

Dove

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à il risultato

1 
2 
3 

o di qualche ordine di lì. Questo perché quando l'attività si avvia infine controllerà il valore corrente del suo riferimento a x. nel primo esempio tutti e 3 i loop puntavano allo stesso riferimento, dove nel secondo esempio puntavano tutti su riferimenti diversi.

2

Come nel caso di molte semplici ottimizzazioni come questa, il compilatore si prende cura di esso per voi. Se si tenta entrambi questi e guardare L'assemblee in ildasm si può vedere che entrambi dichiarano un singolo Int32 leggono variabile, anche se non riordinare le dichiarazioni:

.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) 
Problemi correlati