2009-12-21 11 views
7

Si consideri il seguente codice, il primo dimostra che la "pulizia" viene eseguita quando abbiamo terminato l'iterazione su IEnumerable delle stringhe. Il secondo passaggio è ciò che mi sta causando dolore. Devo essere in grado di abbandonare l'oggetto IEnumerable prima di raggiungere la fine e quindi eseguire il codice di pulizia. Ma se lo fai, vedrai che nel secondo passaggio la pulizia non spara mai.Come posso abbandonare un IEnumerator senza iterare fino alla fine?

Qual è il modo preferito di abbandonare un IEnumerable come questo?

static void Main(string[] args) 
{ 
    // first pass 
    foreach (String color in readColors()) 
     Console.WriteLine(color); 

    // second pass 
    IEnumerator<string> reader = readColors().GetEnumerator(); 
    if (reader.MoveNext()) 
    { 
     Console.WriteLine(reader.Current); 
     reader.Dispose(); 
    } 
} 
static IEnumerable<string> readColors() 
{ 
    string[] colors = { "red", "green", "blue" }; 
    for (int i = 0; i < colors.Length; i++) 
     yield return colors[i]; 

    Console.WriteLine("Cleanup goes here"); 
} 
+1

I può essere manca qualcosa, o il vostro esempio è troppo semplificata ... ma cosa si fa a pulizia Ened a correre alla fine del censimento nel readColors? Sembra che il GC dovrebbe essere in grado di gestire le cose proprio bene. –

+0

Se si dispone di una collezione personalizzata, è possibile implementare anche l'IDisposable?!. – serhio

risposta

7

è necessario mettere la parte principale del tuo metodo iteratore in un try..finally, con il codice di pulitura nel infine:

public IEnumerable<string> readColors() 
    { 
     try 
     { 
      string[] colors = { "red", "green", "blue" }; 
      for (int i = 0; i < colors.Length; i++) 
       yield return colors[i]; 
     } 
     finally 
     { 
      Console.WriteLine("Cleanup goes here"); 
     } 
    } 

ricordi che sotto il cofano un metodo iterator provoca una classe separata da creare, che implementa IEnumerable e IEnumerator. Mettendo la pulizia nel blocco finally, finisce nel metodo Dispose della classe generata.

[Edit: (come sottolineato in altre risposte) preferiscono una dichiarazione using sopra il vostro approccio di chiamare Dispose manualmente. Supponevo che l'avessi fatto in questo modo solo per evidenziare il problema in discussione, ma vale comunque la pena di segnalarlo]

+0

@Luke: Sì, lo è. Questa è la riga 'reader.Dispose();'. – jason

+0

Questa è la risposta corretta, una volta eseguita questa operazione, è possibile semplificare la seconda linea per utilizzare ancora "foreach" anziché il codice più complicato che si ha ora. Basta fare: foreach (String color in readColors()) {Console.WriteLine (color); rompere; } – StarPacker

4

Questo è un modo per abbandonarlo. La ragione per cui non si è visto

Cleanup goes here 

stampato su console è perché il ciclo for (int i = 0; i < colors.Length; i++) mai eseguito fino al completamento. Vedi sotto per come forzare il codice di pulizia da eseguire.

Ecco un altro modo. Questo è il modello preferito per l'utilizzo degli oggetti IDisposable in C#. Ciò è preferibile poiché ciò causerà la chiamata di IEnumerator.Dispose anche se si verifica un'eccezione.

using (IEnumerator<string> reader = readColors().GetEnumerator()) { 
    reader.MoveNext(); 
    Console.WriteLine(reader.Current); 
} 

quanto per forzare il codice di pulitura devi eseguire è possibile effettuare le seguenti operazioni:

static IEnumerable<string> readColors() { 
    string[] colors = { "red", "green", "blue" }; 
    try { 
     for (int i = 0; i < colors.Length; i++) { 
      yield return colors[i]; 
     } 
    } 
    finally { 
     Console.WriteLine("Cleanup goes here"); 
    } 
} 
+0

@Downvoter: dai una ragione. – jason

+0

Quando ho dato il downvote, c'erano solo un paio di righe di risposta che non sembravano avere molto senso. Ora hai chiarito, l'ho rimosso. –

+0

@Rob Levine: ho fatto clic su invia un po 'prematuramente così giusto. Grazie per essere tornato per commentare. – jason

1

Penso che il modo migliore per eseguire la pulizia sia usando IDisposable. In questo caso, è meglio implementare il proprio IEnumerable<string> con uno specifico IEnumerator<string> e utilizzare il normale metodo Dispose. Si ottiene il gratuito quando si utilizza foreach.

class MyEnumerator : IEnumerator<string> 
    { 
     // ... 
     #region IDisposable Members 

     public void Dispose() 
     { 
      // do your cleanup here 
      throw new NotImplementedException(); 
     } 

     #endregion 
     // ... 
    } 
+0

Perché il downvote? –

+1

Non hai bisogno di implementare il tuo - il compilatore lo farà per te fintanto che metti la pulizia in un blocco finale. –

+0

Il tuo probabilmente corretto, e per casi semplici penso che la tua risposta sia migliore. –

0
try { 

    string[] colors = { "red", "green", "blue" }; 
    for (int i = 0; i < colors.Length; i++) { 
    if(condition == true) 
     break; 
    yield return colors[i]; 
    } 
} 
finally { 
    Console.WriteLine("Cleanup goes here"); 
} 
+0

@Luke: Sì, lo è. Questa è la riga 'reader.Dispose();'. – jason

Problemi correlati