На C#: обработка исключений в рекурсивном вызове

у меня есть рекурсивный вызов метода. Когда возникает какое-либо исключение, я хотел бы видеть, где в рекурсивном стеке вызовов это произошло. У меня есть поле, которое содержит "путь", который представляет стек рекурсии.

теперь я хотел бы добавить информацию о пути к любому исключению, которое может быть вызвано рекурсивным вызовом.

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 ответов


Я думаю, вы пытаетесь включить рекурсивный путь в детали исключения, чтобы помочь отладке.

Как насчет попробовать это.

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

если вы используете threading и т. д., Вам может потребоваться посмотреть путь хранения в локальном хранилище потоков.


Если вы не хотите заставлять вызывающего абонента иметь дело с RecursionException, вы можете сделать "путь" общедоступным, чтобы вызывающий абонент мог получить к нему доступ. (Как par Эрик Липперт позже ответ)

или вы можете зарегистрировать путь к вашей системе регистрации ошибок, когда вы поймаете исключение, а затем просто повторно запустите исключение.

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

это имеет то преимущество, что абоненту не нужно знать о "пути" вообще.

все сводится к тому, что ваш абонент нуждается, как-то я думаю, что вы вызывающий для этого кода, поэтому нет смысла пытаться 2nd угадать, что нужно на этом уровне сделки.


просто упрощая (немного) обработку исключений:

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

вы получаете информацию о 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();
    }
}

Как насчет выдергивания обработчика 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);
}

ваша проблема заключается в обработке исключений. Обертывание исключения в собственное исключение обычно является плохой идеей, потому что это накладывает бремя на вызывающего код, чтобы обработать ваше исключение. Вызывающий, который подозревает, что они могут, скажем, вызвать исключение "путь не найден", вызывая ваш код, не может обернуть свой вызов в try-catch, который ловит IOException. Они должны поймать ваше 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

видишь, что мы здесь делаем? Мы используем тот факт, что исключение останавливает рекурсивный алгоритм в его треках. Последнее, чего мы хотим. сделать это очистить путь, выскакивая в конце концов; мы хочу the pops, чтобы быть потерянным на исключение!

смысл?


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 обрабатывает его, рекурсия прерывается.