2009-10-19 30 views
6

Tengo una llamada de método recursivo. Cuando se lanza una excepción, me gustaría ver dónde ocurrió la pila de llamadas recursivas. Tengo un campo que contiene un "camino" que representa la pila de recursión.C#: manejo de excepciones en llamada recursiva

Ahora me gustaría agregar la información de ruta a cualquier excepción que pueda ser lanzada en la llamada recursiva.

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

Parece demasiado complicado. No hay solo un método Probablemente haya veinte o incluso más lugares donde tuve que escribir este código.

¿Hay alguna manera más simple de implementar esto?


Editar: Para señalar esto: Me gustaría tener una situación mucho más simple donde no hay una sobrecarga tal que llamar de forma recursiva del método, porque tengo muchas de esas llamadas recursivas, no sólo hay un método, hay un par de métodos recursivamente llamando entre sí, que es lo suficientemente complejo.

Así que me gustaría evitar todo el bloque try - catch, pero no veo ninguna solución para esto.

No es un gran problema para las excepciones lanzadas en mi propio código, ya que podría incluir la ruta desde el principio. Pero es un problema con cualquier otra excepción.


Editar: Las excepciones deben ser envuelto en cualquier otro código, no sólo al llamar al método recursivo:

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

    Recursive(x + 6); 

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

Así envolver sólo la llamada a Recusive no funciona.

existen muchos de estos métodos, con diferentes firmas, haciendo cosas diferentes, lo único común es el manejo de excepciones.

+2

¿cuál es la diferencia entre su "ruta" y la propiedad callstack que está presente en Exception? –

+0

La pila de llamadas solo consiste en nombres de métodos, que siempre son los mismos. En este caso, sería "Recursivo -> Recursivo -> Recursivo -> Recursivo" que no dice nada. La recursión en realidad está descendiendo por un árbol, y la ruta representa el camino a la rama actual en una forma legible por humanos. –

+0

Haciendo todo el trabajo en métodos que se pasan a Recursive estaría envuelto en excepciones usando mi método extendido, tratar con diferentes firmas es complicado, aunque puede salirse con la suya usando genéricos para especificar tipos de parámetros, pero las firmas arbitrarias pueden no será posible sin escribir sobrecargas para cada ... –

Respuesta

2

Creo que está intentando incluir la ruta recursiva en los detalles de la excepción para ayudar a la depuración.

¿Qué tal intentar esto.

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

Si está usando threading, etc, se puede tener que mirar en el almacenamiento de ruta en hilo de almacenamiento local.


Si no desea forzar a la persona que llama para hacer frente a RecursionException, podría hacer que el público “camino” por lo que la persona que llama puede acceder a él. (Como el par Eric Lippert responderá más tarde)

O puede registrar la ruta a su sistema de registro de errores cuando detecta la excepción y luego vuelve a lanzar la excepción.

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

Esto tiene la ventaja de que la persona que llama no necesita conocer la "ruta" en absoluto.

Todo se reduce a lo que su interlocutor necesita, de alguna manera creo que usted es el que llama para este código, por lo que no tiene sentido tratar de adivinar lo que se necesita en este nivel de trato.

+0

Sí, esto realmente lo simplificará. En el caso de una excepción, solo conserva la ruta y la maneja solo en la parte superior. De hecho, debo eliminar todos los manejadores de excepciones y, finalmente, los bloques para que esto funcione, pero la eliminación de código siempre es fácil :-) –

+0

Por cierto: la clase no es guardar el hilo de todos modos. –

5

Sólo simplificando (ligeramente) el manejo de excepciones:

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

Se obtiene información pila de llamadas con excepción si eso es de ninguna utilidad para usted, más allá de que se podría escribir esto como un fragmento a continuación, basta con insertar ahí donde necesidad de volver a usar.

También existe la siguiente posibilidad, que sería lento pero debería funcionar:

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í, por supuesto, esto soluciona el tonto 'is'. Pero, en realidad, me gustaría evitar todo el intento de capturar cosas, lo cual es doloroso de mantener en tantos lugares. –

+0

He agregado una versión que funcionará pasando a un delegado que toma un argumento int y devuelve un bool. De esta forma, puede pasar un método que hará el trabajo real y luego devolver si continúa o no la recursión. En ese punto, su método de recursión real será solo un marco para hacer el trabajo pesado. Sin embargo, será más lento que el código anterior. –

+0

Todos los métodos recursivos tienen diferentes firmas. Si tuviera que dividir cada uno de ellos, lo haría aún más complicado. –

4

¿Qué hay de un tirón el controlador de captura de la función recursiva y sólo escribir la recursividad y sin menos de la manipulación?

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

, pero ¿cómo se obtienen detalles de la "ruta" en la excepción? –

+0

No puedo ver el punto. ¿Dónde se usa StartRecursion? ¿Dónde está path.Pop? –

+0

Revisa las ediciones. –

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

De esa manera sólo manejan una vez, si una excepción plantea Recursive2 lo maneja, la recursividad se aborta.

+0

, pero ¿cómo se obtienen detalles de la "ruta" en la excepción? –

4

Tu problema está en el manejo de excepciones. En general, envolver una excepción en su propia excepción es una mala idea porque impone una carga a la persona que llama de su código para que tenga que encargarse de su excepción. Una persona que llama que sospecha que podría, por ejemplo, estar causando una excepción de "ruta de acceso no encontrada" llamando a su código no puede ajustar su llamada en un try-catch que atrapa IOException. Deben atrapar su RecursionException y luego escribir un montón de código para interrogarlo y determinar qué tipo de excepción fue en realidad. Hay momentos en que este patrón está justificado, pero no veo que este sea uno de ellos.

La cuestión es que no es necesario que utilice aquí el control de excepciones. Aquí están algunos aspectos deseables de una solución:

  • persona que llama puede coger cualquier tipo de excepción que quieren
  • en versión de depuración, llamador puede determinar la información acerca de lo que la función recursiva estaba haciendo cuando se produce una excepción.

OK, genial, si esos son los objetivos de diseño, a continuación, poner en práctica lo siguiente:

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

Y ahora la persona que llama puede tratar con él:

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

#if DEBUG

// do something with c.Path 

Ves lo que estamos haciendo aquí? Estamos aprovechando el hecho de que una excepción detiene el algoritmo recursivo en sus pistas.Lo último que queremos hacer es limpiar el camino apareciendo finalmente; nosotros queremos ¡las apariciones se pierden con una excepción!

¿Tiene sentido?

+0

Sí, esto tiene sentido. Ian Ringrose ya sugirió algo así y acepté su respuesta. Por cierto, la ruta siempre está ahí, no la necesito solo para la excepción y no es una función de solo depuración. Pero esto no tiene importancia para la solución. ¡Gracias de todos modos! –

+0

uso del atributo condicional es neet, sin embargo, a menudo he necesitado este breve de información de depuración para problemas del cliente, necesita pensar caso por caso. –