我有一个递归方法调用。当抛出任何异常时,我想看看,在递归调用堆栈中它发生了什么。我有一个字段,其中包含一个表示递归堆栈的“路径”。
现在我想将路径信息添加到可能在递归调用中抛出的任何异常。
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的调用不起作用。
有很多这样的方法,有不同的签名,做不同的事情,唯一常见的是异常处理。
答案 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处理它,递归就会中止。