2009-10-19 17 views
6

Ho una chiamata al metodo ricorsivo. Quando viene lanciata un'eccezione, mi piacerebbe vedere dove è successo lo stack di chiamate ricorsive. Ho un campo che contiene un "percorso" che rappresenta lo stack di ricorsione.C#: gestione delle eccezioni in chiamata ricorsiva

Ora desidero aggiungere le informazioni sul percorso a qualsiasi eccezione che possa essere generata durante la chiamata ricorsiva.

void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
    // do some stuff and recursively call the method 
    Recursive(x + 6); 
    } 
    catch(Exception ex) 
    { 
    if (ex is RecursionException) 
    { 
     // The exception is already wrapped 
     throw; 
    } 
    // wrap the exception, this should be done only once. 
    // save the path and original exception to the wrapper. 
    throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
    // maintain the recursion path information 
    path.Pop() 
    } 
} 

Sembra troppo complicato. Non c'è solo un metodo. Ci sono probabilmente venti o anche più posti in cui ho dovuto scrivere questo codice.

C'è un modo più semplice per implementare questo?


Modifica: Per sottolineare questo: mi piacerebbe avere una situazione molto più semplice in cui non c'è un tale sovraccarico di chiamare in modo ricorsivo il metodo, perché ho molte di queste chiamate ricorsive, non c'è solo un metodo, ci sono un paio di metodi che chiamano ricorsivamente l'un l'altro, che è abbastanza complesso.

Quindi vorrei evitare l'intero blocco try-catch, ma non riesco a vedere alcuna soluzione per questo.

Non è un grosso problema per Eccezioni generate nel mio codice, perché potrebbe includere il percorso dall'inizio. Ma è un problema con ogni altra eccezione.


Edit: Le eccezioni devono essere avvolto in qualsiasi altro codice, non solo quando si chiama il metodo ricorsivo:

try 
    { 
    int a = 78/x; // DivisionByZeroExeption   

    Recursive(x + 6); 

    this.NullReference.Add(x); // NullReferenceException 
    } 

Quindi avvolgere solo la chiamata per Recusive non funziona.

ci sono molti di questi metodi, con diverse firme, fare cose diverse, l'unica cosa comune è la gestione delle eccezioni.

+2

Qual è la differenza tra il "percorso" e la proprietà del callstack presente su Eccezione? –

+0

Il callstack consiste solo di nomi di metodi, che sono sempre gli stessi. In questo caso, sarebbe "Ricorsivo -> Ricorsivo -> Ricorsivo -> Ricorsivo" che non dice nulla. La ricorsione sta effettivamente scendendo lungo un albero e il percorso rappresenta il percorso verso il ramo corrente in una forma leggibile dall'uomo. –

+0

Fare tutto il lavoro nei metodi che sono passati attraverso Recursive sarebbe racchiuso tra le eccezioni usando il mio metodo esteso, trattare le diverse firme è complicato, sebbene si possa essere in grado di cavarsela usando i generici per specificare i tipi di parametri, ma le firme arbitrarie potrebbero non è possibile senza scrivere sovraccarichi per ogni ... –

risposta

2

Penso che si stia tentando di includere il percorso ricorsivo nei dettagli delle eccezioni in modo da facilitare il debug.

Che ne dici di provarlo.

public void Recursive(int x) 
{ 
    try 
    { 
    _Recursive(x) 
    } 
    catch 
    { 
    throw new RecursionException(path.ToString(), ex); 
    clear path, we know we are at the top at this point 
    } 
} 

private void _Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    _Recursive(x + 6); 

    //maintain the recursion path information 
    //note this is not in a catch so will not be called if there is an exception 
    path.Pop() 
} 

Se si utilizza threading, ecc, si può guardare a immagazzinare percorso nella memoria locale thread.


Se non si desidera imporre il vostro interlocutore per affrontare RecursionException, si potrebbe fare il pubblico “percorso” in modo che il chiamante possa accedervi. (Come risponde Eric Lippert in seguito)

Oppure è possibile registrare il percorso del sistema di registrazione degli errori quando si rileva l'eccezione e quindi basta lanciare nuovamente l'eccezione.

public void Recursive(int x) 
{ 
    try 
    { 
    _Recursive(x) 
    } 
    catch 
    { 
    //Log the path to your loggin sysem of choose 
    //Maybe log the exception if you are not logging at the top 
    // of your applicatoin   
    //Clear path, we know we are at the top at this point 
    } 
} 

Questo ha il vantaggio che il chiamante non ha bisogno di conoscere il "percorso" del tutto.

Tutto si riduce a ciò di cui il tuo interlocutore ha bisogno, in qualche modo penso che tu sia il chiamante per questo codice, quindi non ha senso tentare di indovinare in secondo luogo ciò che è necessario a questo livello di accordo.

+0

Sì, questo in realtà lo semplificherà. In caso di eccezione, mantiene solo il percorso e lo gestisce solo in alto. In realtà devo rimuovere tutti i gestori di eccezioni e infine i blocchi per farlo funzionare, ma la rimozione del codice è sempre semplice :-) –

+0

Btw: la classe non è il salvataggio del thread in ogni caso. –

5

Proprio semplificando (leggermente) la gestione delle eccezioni:

void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
     // do some stuff and recursively call the method 
     Recursive(x + 6); 
    } 
    catch(RecursionException) 
    { 
     throw; 
    } 
    catch(Exception) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
     // maintain the recursion path information 
     path.Pop() 
    } 
} 

Si ottiene informazioni stack con l'eccezione, se questo è di alcuna utilità per voi, al di là che si potrebbe scrivere questo come un frammento poi basta inserire che in cui si necessità di riutilizzabilità.

C'è anche la seguente possibilità, che sarebbe lento ma dovrebbe funzionare:

void DoStuff() 
{ 
    this.Recursive(1, this.RecursiveFunction1); 
    this.Recursive(2, this.RecursiveFunction2); 
} 

bool RecursiveFunction1(int x) 
{ 
    bool continueRecursing = false; 

    // do some stuff 
    return continueRecursing; 
} 

bool RecursiveFunction2(int y) 
{ 
    bool continueRecursing = false; 

    // do some other stuff here 
    return continueRecursing; 
} 

private void Recursive(int x, Func<int, bool> actionPerformer) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
     // recursively call the method 
     if(actionPerformer(x)) 
     { 
      Recursive(x + 6, actionPerformer); 
     } 
    } 
    catch(RecursionException) 
    { 
     throw; 
    } 
    catch(Exception ex) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
    finally 
    { 
     // maintain the recursion path information 
     path.Pop(); 
    } 
} 
+0

Sì, certo, questo risolve lo sciocco 'is'. Ma in realtà, vorrei evitare tutta la roba da prendere, che è doloroso mantenere in così tanti posti. –

+0

Ho aggiunto una versione che funzionerà passando un delegato che accetta un argomento int e restituisce un bool. In questo modo puoi passare in un metodo che farà il lavoro vero e poi tornare o meno a ricorrere. A quel punto il tuo metodo di ricorsività reale sarà solo una struttura per fare il sollevamento pesi. Tuttavia, sarà più lento del precedente bit di codice. –

+0

I metodi ricorsivi hanno tutti diverse firme. Se dovessi dividere ciascuno di essi, lo renderebbe ancora più complicato. –

4

Che dire tirando il gestore catch fuori dalla funzione ricorsiva e solo scrivendo la ricorsione, senza meno del trattamento?

void StartRecursion(int x) 
{ 
    try 
    { 
     path.Clear(); 
     Recursive(x); 
    } 
    catch (Exception ex) 
    { 
     throw new RecursionException(path.ToString(), ex); 
    } 
} 

void Recursive(int x) 
{ 
    path.Push(x); 
    Recursive(x + 6); 
    path.Pop(); 
} 

void Main() 
{ 
    StartRecursion(100); 
} 
+0

ma come si ottengono i dettagli del "percorso" nell'eccezione? –

+0

Non riesco a vedere il punto. Dove viene utilizzato StartRecursion? Dov'è path.Pop? –

+0

Controllare le modifiche. –

0
void Recursive(int x) 
{ 
    // maintain the recursion path information 
    path.Push(x); 

    try 
    { 
    // do some stuff and recursively call the method 
    Recursive(x + 6); 
    } 
    finally 
    { 
    // maintain the recursion path information 
    path.Pop() 
    } 
} 

void Recursive2(int x) 
{ 
    try 
    { 
    Recursive(x); 
    } 
    catch() 
    { 
     // Whatever 
    } 
} 

Questo modo di gestire solo una volta, se un'eccezione solleva Recursive2 gestisce, la ricorsione viene interrotta.

+0

ma come si ottengono i dettagli del "percorso" nell'eccezione? –

4

Il problema è nella gestione delle eccezioni. Avvolgere un'eccezione nella propria eccezione è in genere una cattiva idea, perché mette a carico del chiamante del codice il compito di gestire l'eccezione. Un chiamante che sospetta che potrebbe, ad esempio, causare un'eccezione "percorso non trovato" chiamando il codice non può eseguire il wrapping della chiamata in un try-catch che cattura IOException. Devono prendere la tua RicorsioneException e poi scrivere un mucchio di codice per interrogarlo per determinare quale tipo di eccezione fosse realmente. Ci sono momenti in cui questo schema è giustificato, ma non vedo che questo è uno di loro.

Il fatto è che non è affatto necessario utilizzare la gestione delle eccezioni qui. Qui ci sono alcuni aspetti desiderabili di una soluzione:

  • chiamante può prendere qualunque tipo di eccezione che vogliono
  • in build di debug, chiamante può determinare le informazioni su ciò che la funzione ricorsiva stava facendo quando un'eccezione è stato gettato.

OK, grande, se questi sono gli obiettivi di progettazione, quindi implementare che:

class C 
{ 
    private Stack<int> path 

#if DEBUG

= new Stack<int>(); 

#else

= null; 

#endif

public Stack<int> Path { get { return path; } } 

    [Conditional("DEBUG")] private void Push(int x) { Path.Push(x); } 
    [Conditional("DEBUG")] private void Pop() { Path.Pop(); } 
    public int Recursive(int n) 
    { 
    Push(n); 
    int result = 1; 
    if (n > 1) 
    { 
     result = n * Recursive(n-1); 
     DoSomethingDangerous(n); 
    } 
    Pop(); 
    return result; 
    } 
} 

Ed ora il chiamante può trattare con esso:

C c = new C(); 
try 
{ 
    int x = c.Recursive(10); 
} 
catch(Exception ex) 
{ 

#if DEBUG

// do something with c.Path 

Vedete quello che stiamo facendo qui? Stiamo sfruttando il fatto che un'eccezione arresta l'algoritmo ricorsivo nelle sue tracce.L'ultima cosa che vogliamo fare è ripulire il percorso scoppiando in un finalmente; noi vogliamo i pop da perdere su un'eccezione!

Ha senso?

+0

Sì, questo ha senso. Ian Ringrose ha già suggerito qualcosa del genere e ho accettato la sua risposta. A proposito, il percorso è sempre lì, non ne ho bisogno solo per l'eccezione e non è una funzionalità di debug-only. Ma questo non ha importanza per la soluzione. Grazie lo stesso! –

+0

l'uso dell'attrubute condizionale è neet, tuttavia ho spesso avuto bisogno di informazioni di debug per i problemi dei clienti, è necessario riflettere caso per caso. –