2009-11-15 6 views
7

Ho usato per creare interfacce con IEnumerable<T> come tipo di ritorno, ogni volta che voglio specificare che un particolare output è di sola lettura. Mi piace perché è minimalista, nasconde i dettagli di implementazione e disaccoppia un callee da un chiamante.Un problema con la gestione delle eccezioni per IEnumerable <T>, è pigro-dipendente

Ma di recente un mio collega ha sostenuto che IEnumerable<T> dovrebbero essere tenuti per gli scenari che coinvolgono valutazione pigra solo, altrimenti non è chiaro per un metodo chiamante, in cui la gestione delle eccezioni dovrebbe prendere il suo posto - intorno a una chiamata di metodo o intorno ad un iterazione. Quindi per i casi di valutazione desiderosi con un'uscita di sola lettura, dovrei usare uno ReadOnlyCollection.

Suoni abbastanza ragionevoli per me, ma cosa consiglieresti? Sei d'accordo con quella convenzione per IEnumerable? O ci sono metodi migliori per la gestione delle eccezioni attorno a IEnumerable?

Nel caso in cui la mia domanda non sia chiara, ho creato una classe di esempio che illustra il problema. Due metodi in qui hanno esattamente la stessa firma, ma richiedono diversa gestione delle eccezioni:

public class EvilEnumerable 
{ 
    IEnumerable<int> Throw() 
    { 
     throw new ArgumentException(); 
    } 

    IEnumerable<int> LazyThrow() 
    { 
     foreach (var item in Throw()) 
     { 
      yield return item; 
     } 
    } 

    public void Run() 
    { 
     try 
     { 
      Throw(); 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("immediate throw"); 
     } 

     try 
     { 
      LazyThrow(); 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("No exception is thrown."); 
     } 

     try 
     { 
      foreach (var item in LazyThrow()) 
      { 
       //do smth 
      } 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("lazy throw"); 
     } 
    } 
} 

Update 1. La questione non si limita alla sola ArgumentException. Si tratta delle migliori pratiche per creare interfacce di classe intuitive, che ti diano una risposta se restituiscono risultati di valutazione pigri o meno, perché questo influenza l'approccio di gestione delle eccezioni.

+5

È assurdo supporre che IEnumerable debba essere tenuto a una valutazione lenta, in quanto non è mai stato collegato alla valutazione pigra in primo luogo. IEnumerable esisteva prima che la valutazione lazy fosse un paradigma comune in C#. – Joren

risposta

9

Il vero problema qui è l'esecuzione differita. Nel caso del controllo degli argomenti, è possibile farlo aggiungendo un secondo metodo:

IEnumerable<int> LazyThrow() { 
    // TODO: check args, throwing exception 
    return LazyThrowImpl(); 
} 
IEnumerable<int> LazyThrowImpl() { 
    // TODO: lazy code using yield 
} 

Eccezioni; anche per risultati non differiti (List<T>, ad esempio) è possibile ottenere errori (forse se un altro thread regola l'elenco mentre si sta iterando). L'approccio sopra riportato consente di fare il più possibile in anticipo per ridurre gli effetti collaterali imprevisti di yield e l'esecuzione posticipata.

+1

Beh, questo è un frammento utile, ma la mia domanda non era in realtà limitati alla verifica args, qualsiasi eccezione può accadere durante la valutazione pigra stesso (nel LazyThrowImpl che è). Quindi quello che mi chiedo è se sarebbe una convenzione ragionevole mantenere IEnumerable solo per i casi di valutazione lazy, aiutando così i chiamanti a capire dove deve essere il codice di gestione delle eccezioni. –

+1

Il mio punto è che qualsiasi eccezione può accadere anche durante la valutazione * regolare *, quindi è un punto controverso. OK * di più * generalmente accade nella valutazione pigra, ma direi * in generale * di non preoccuparsene. –

2

Non sono d'accordo con il tuo collega. La documentazione del metodo shoul segue lo standard di un documento che eccezioni potrebbe lanciare. La dichiarazione del tipo restituito come IEnumerable non lo rende di sola lettura. Indipendentemente dalla sua lettura dipende solo dall'attuale implementazione dell'interfaccia restituita. Il chiamante non dovrebbe fare affidamento su di essa essere scrivibile, ma che è molto diversa dalla raccolta in fase di sola lettura

+0

Mi dispiace, capisco quello che stai dicendo, ma non vedo come si risponde alla domanda ( –

+2

@yacoder il mio punto è che nessuno IEnumerable non aveva niente a che fare con la valutazione pigra. Tutte le collezioni generiche BCL implementano tale interfaccia e Lista e simili è mit pigramente valutati. Inoltre dichiarando il tipo di ritorno non dice che _is_ sola lettura. si dice solo che l'unica cosa che si può contare su sta ottenendo un enumeratore –

0

problema simile è stato discusso nei post di Eric Lippert:

http://blogs.msdn.com/ericlippert/archive/2007/09/05/psychic-debugging-part-one.aspx

http://blogs.msdn.com/ericlippert/archive/2007/09/06/psychic-debugging-part-two.aspx

Tuttavia, non lo farò trattalo come argomento contro IEnumerable in generale. Per essere onesti, la maggior parte delle volte in cui la raccolta che sto enumerando genera un'eccezione, non so cosa farne e fondamentalmente va a un gestore di errori globale. Naturalmente, sono d'accordo sul fatto che il momento in cui viene lanciata l'eccezione potrebbe essere fonte di confusione.

+0

la mia domanda è esattamente sul momento l'eccezione è gettato. e 'impossibile capire solo dall'interfaccia se il metodo sta per lanciare la sua eccezione, quando lo chiamo io, o quando ho iterare il suo risultato. –

+0

Cioè, quando si utilizza IEnumerable per tutto. –

3

In teoria, sono d'accordo con il collega. Se c'è un po 'di' lavoro 'per creare i risultati, e che il lavoro può causare un'eccezione, e non è necessaria una valutazione lenta, allora rendere il risultato pigro complicherà solo le cose.

Tuttavia, in pratica, non è possibile visualizzare il tipo restituito e dedurre qualcosa di troppo utile.Anche se hai fatto il tipo di ritorno ReadOnlyCollection, ancora potrebbe ritardare la valutazione e gettare, per esempio (ReadOnlyCollection non è sigillato, quindi una sottoclasse potrebbe implementare in modo esplicito l'interfaccia IEnumerable e fare cose stravaganti).

Alla fine della giornata, il sistema di tipo Net non sta andando per aiutarvi a ragione molto circa il comportamento eccezione della maggior parte degli oggetti.

Vorrei, in effetti, scegliere ancora di restituire un oggetto ReadonlyCollection anziché un oggetto IEnumerable, ma perché espone metodi più utili (come O (1) accesso all'elemento nth) rispetto a IEnumerable. Se stai producendo una raccolta manifest supportata da un array, potresti anche restituire ai consumatori aspetti utili di ciò. (Si potrebbe anche solo restituire una matrice "fresca" che il chiamante deve possedere.)

+0

Beh, sicuramente, si possono fare cose cattive anche con ReadOnlyCollection, ma è così Vorrei un po 'di sforzo. Mentre lanciando un pigro valutato IEnumerable contro desideroso valutato è senza sforzo. return myInternalCollection.Select (...) vs return myInternalCollection.Select(). ToList() –

Problemi correlati