C#:递归调用中的异常处理

时间:2009-10-19 12:21:09

标签: c# exception-handling recursion

我有一个递归方法调用。当抛出任何异常时,我想看看,在递归调用堆栈中它发生了什么。我有一个字段,其中包含一个表示递归堆栈的“路径”。

现在我想将路径信息添加到可能在递归调用中抛出的任何异常。

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

看起来太复杂了。不仅有一种方法。可能有二十个甚至更多的地方我必须编写这段代码。

有没有更简单的方法来实现它?


编辑:要指出这一点:我希望有一个更简单的情况,即没有这样的开销来递归调用该方法,因为我有很多这样的递归调用,没有只有一种方法,有几种方法递归地相互调用,这很复杂。

所以我想避免整个try-catch块,但我看不到任何解决方案。

对于我自己的代码中抛出的异常,这不是一个大问题,因为它可能包含从头开始的路径。但这是其他所有例外的问题。


编辑:异常需要包含在任何其他代码中,而不仅仅是在调用递归方法时:

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

    Recursive(x + 6);

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

因此只包含对Recusive的调用不起作用。

有很多这样的方法,有不同的签名,做不同的事情,唯一常见的是异常处理。

5 个答案:

答案 0 :(得分:5)

简单地(稍微)简化异常处理:

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

如果对您有任何用处,您可以使用Exception获取callstack信息,除此之外您可以将其作为片段写入,然后只需将其插入您需要重新使用的位置。

还有以下可能性,这种可能性很慢但应该有效:

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

答案 1 :(得分:4)

如何将catch处理程序从递归函数中移除,只需编写递归而不需要更少的处理?

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

答案 2 :(得分:4)

您的问题在于异常处理。在您自己的异常中包装异常通常是一个坏主意,因为它会给代码调用者带来负担,必须处理异常。一个调用者怀疑他们可能会通过调用代码导致“找不到路径”异常,但无法将其调用包装在捕获IOException的try-catch中。他们必须抓住你的RecursionException,然后编写一堆代码来询问它,以确定它究竟是什么样的异常。有时这种模式是合理的,但我不认为这是其中之一。

问题是,你真的没必要在这里使用异常处理。以下是解决方案的一些理想方面:

  • 调用者可以捕获他们想要的任何类型的异常
  • 在调试版本中,调用者可以确定抛出异常时递归函数正在做什么的信息。

好的,很好,如果这些是设计目标,那么实现:

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

现在调用者可以处理它:

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

#if DEBUG

  // do something with c.Path

你看到我们在这里做什么?我们正在利用异常在其轨道中停止递归算法的事实。我们要做的最后一件事是通过弹出最终来清理路径;我们想要在异常时丢失pop!

有意义吗?

答案 3 :(得分:2)

我认为您正在尝试在异常详细信息中包含递归路径,以便帮助调试。

尝试这个怎么样。

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

如果您正在使用线程等,则可能需要查看线程本地存储中的存储路径。


如果您不希望强制调用者处理RecursionException,您可以将“path”公开,以便调用者可以访问它。 (正如Eric Lippert后来的回答)

或者,当您捕获异常然后重新抛出异常时,您可以将路径记录到错误记录系统。

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

这样做的好处是,来电者根本不需要知道“路径”。

这一切都取决于你的调用者需要什么,不知何故我认为你是这个代码的调用者,所以我们没有必要尝试第二次猜测这个交易水平需要什么。

答案 4 :(得分: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
  }
}

这样你只处理一次,如果异常引发Recursive2处理它,递归就会中止。