views:

919

answers:

5

I have a recursive method call. When any exception is thrown, I would like to see, where in the recursive call stack it happened. I have a field which holds a "path" which represents the recursion stack.

Now I would like to add the path information to any exception that could possibly be thrown in the recursive call.

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

It just looks too complicated. There is not only one method. There are probably twenty or even more places where I had to write this code.

Is there any simpler way to implement this?


Edit: To point this out: I would like to have a much simpler situation where there is not such an overhead to recursively call the method, because I have many such recursive calls, there is not only one method, there are a couple of methods recursively calling each other, which is complex enough.

So I would like to avoid the whole try - catch block, but I can't see any solution for this.

It is not a big problem for Exceptions thrown in my own code, because it could include the path from the beginning. But it is a problem with every other exception.


Edit: The Exceptions need to be wrapped in any other code, not only when calling the recursive method:

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

    Recursive(x + 6);

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

So wrapping only the call to Recusive does not work.

there are many such methods, having different signatures, doing different things, the only common thing is the exception handling.

+5  A: 

Just simplifying (slightly) the exception handling:

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

You get callstack info in with Exception if that's of any use to you, beyond that you could write this as a snippet then just insert that where you need to for re-usability.

There's also the following possibility, which would be slow but should work:

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();
    }
}
Dave
Yes, of course, this fixes the silly `is`. But actually, I would like to avoid the whole try - catch stuff, which is painful to maintain at so many places.
Stefan Steinegger
I've added a version that will work by passing a delegate that takes an int argument and returns a bool. That way you can pass in a method that will do the actual work then return whether or not to continue recursing. At that point your actual recursion method will just be a framework for doing the heavy lifting. It'll be slower than the previous bit of code though.
Dave
The recursive methods all have different signatures. If I had to split each of them, it would just make it even more complicated.
Stefan Steinegger
Do they all have different return types or can they always return whether to continue recursing? If they do have different return types are you able to move the return value to an output parameter?
Dave
They could be as different as they want to be ... I actually don't want to make calling them more complicated. The recursive call structure is already complex enough.
Stefan Steinegger
The only ways I can think of calling abitrary methods inside a recursive framework would make calling them more complicated... :/ The simpler way is to make the recursion code a snippet to wrap around the methods. It's the arbitrary signatures that's the problem, otherwise you could use delegates.
Dave
I found this simple, I like it.
yelinna
+4  A: 

What about yanking the catch handler out of the recursive function and just writing the recursion without less of the handling?

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);
}
John Fisher
but how do you get details of the "path" into the exception?
Ian Ringrose
I can't see the point. Where is StartRecursion used? Where is path.Pop?
Stefan Steinegger
Check the edits.
John Fisher
Ok, here we are actually on Ian Ringroses solution: keeping the path in case of an exception. Thanks.
Stefan Steinegger
A: 
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
  }
}

That way you only handle once, if an exception raises Recursive2 handles it, the recursion is aborted.

Jorge Córdoba
but how do you get details of the "path" into the exception?
Ian Ringrose
+2  A: 

I think you are trying to include the recursive path in the exception details so as to aid debugging.

What about trying this.

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

If you are using threading etc, you will may have to look at storing path in thread local storage.


If you don’t wish to force your caller to deal with RecursionException, you could make the “path” public so the caller can access it. (As par Eric Lippert later answer)

Or you could log the path to your error logging system when you catch the exception and then just re-throw the exception.

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

This has the advantage that the caller does not need to know about the “path” at all.

It all comes down to what your caller needs, somehow I think you are the caller for this code, so there is no point us trying to 2nd guess what is needed at this level of deal.

Ian Ringrose
Yes, this will actually simplify it. In case of an exception, it just keeps the path and handles it only on the top. I actually must remove all the exception handlers and finally blocks to make this work, but removing code is always easy :-)
Stefan Steinegger
Btw: the class is not thread save anyway.
Stefan Steinegger
+4  A: 

Your problem is in the exception handling. Wrapping an exception in your own exception is generally a bad idea because it puts a burden upon the caller of your code to have to handle your exception. A caller that suspects that they might, say, be causing a "path not found" exception by calling your code cannot wrap their call in a try-catch which catches IOException. They have to catch your RecursionException and then write a bunch of code to interrogate it to determine what kind of exception it really was. There are times when this pattern is justified, but I don't see that this is one of them.

The thing is, it's really unnecessary for you to use exception handling at all here. Here are some desirable aspects of a solution:

  • caller can catch whatever kinds of exception they want
  • in debug build, caller can determine information about what the recursive function was doing when an exception was thrown.

OK, great, if those are the design goals, then implement that:

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

And now the caller can deal with it:

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

#if DEBUG

  // do something with c.Path

You see what we're doing here? We're taking advantage of the fact that an exception stops the recursive algorithm in its tracks. The last thing we want to do is clean up the path by popping in a finally; we want the pops to be lost on an exception!

Make sense?

Eric Lippert
Yes, this makes sense. Ian Ringrose already suggested something like this and I accepted his answer.By the way, the path is always there, I don't need it only for the exception and it is not a debug-only feature. But this does not matter for the solution. Thank you anyway!
Stefan Steinegger
use of the Conditional attrubute is neet, however I have often needed this short of debuging information for customer problems, it needs thinking about on a case by case basis.
Ian Ringrose