代码之家  ›  专栏  ›  技术社区  ›  Stefan Steinegger

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

  •  6
  • Stefan Steinegger  · 技术社区  · 15 年前

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

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

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

    它看起来太复杂了。不止一种方法。可能有20个甚至更多的地方需要我编写代码。

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


    编辑 :要指出这一点:我希望有一个更简单的情况,在这种情况下没有递归调用方法的开销,因为我有许多这样的递归调用,不仅有一个方法,还有两个方法递归调用对方,这非常复杂。

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

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


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

      try
      {
        int a = 78 / x; // DivisionByZeroExeption        
    
        Recursive(x + 6);
    
        this.NullReference.Add(x); // NullReferenceException
      }
    

    因此,仅包装对回避的调用是不起作用的。

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

    5 回复  |  直到 15 年前
        1
  •  2
  •   Ian Ringrose    15 年前

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

    试试这个怎么样?

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

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


    如果您不希望强制调用者处理递归异常,您可以将__路径__设为公用,以便调用者可以访问它。(正如埃里克·利珀特后来的回答)

    或者,您可以在捕获异常时记录错误日志系统的路径,然后重新抛出异常。

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

    这样做的好处是,调用者根本不需要知道__路径_。

    归根结底,这都取决于您的调用者需要什么,不知何故,我认为您是这段代码的调用者,所以我们没有必要再去猜测这一级别的交易需要什么。

        2
  •  5
  •   Dave D    15 年前

    只是简化(稍微)异常处理:

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

    如果这对你有任何用处的话,你会得到调用堆栈信息,除此之外,你可以把它写成一个片段,然后把它插入到你需要的地方,以便重新可用。

    还有以下可能性,可能会很慢,但会起作用:

    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();
        }
    }
    
        3
  •  4
  •   John Fisher    15 年前

    将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);
    }
    
        4
  •  4
  •   Eric Lippert    15 年前

    您的问题在异常处理中。在自己的异常中包装一个异常通常是一个坏主意,因为它给代码调用方增加了处理异常的负担。如果调用方怀疑它们可能会通过调用代码导致“找不到路径”异常,则无法将其调用包装在捕获IOException的try catch中。他们必须捕获递归异常,然后编写一组代码来询问它,以确定它实际上是什么类型的异常。有时这种模式是合理的,但我不认为这是其中之一。

    问题是,这里根本不需要使用异常处理。以下是解决方案的一些理想方面:

    • 调用者可以捕获他们想要的任何类型的异常
    • 在调试构建中,调用方可以确定有关引发异常时递归函数正在执行的操作的信息。

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

    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)
    {
    

    如果调试

      // do something with c.Path
    

    你看到我们在这里做什么了吗?我们利用了这样一个事实:异常会阻止递归算法的运行。我们要做的最后一件事是通过最后一次突然出现来清理道路;我们 希望 在一个例外情况下,Pops会丢失!

    有道理?

        5
  •  0
  •   Jorge Córdoba    15 年前
    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
      }
    }
    

    这样,您只处理一次,如果一个异常引发递归2处理它,则递归将中止。