有没有办法在c#

时间:2017-11-17 13:56:06

标签: c# recursion

我正在使用大型复杂的事件驱动代码体,并且有很多机会意外地创建递归条件。

有时递归条件是临时的,应用程序会自动赶上,但即便这样也会造成不必要的延迟。其他时候它会创建一个stackoverflow,当它在客户端站点发生时通常很难调试。

我希望有一种方法可以将黑名单列入白名单或将其列入允许递归的代码段。如果在DEV期间发生递归条件,那么我希望它断言以便我可以更正代码。

我正在考虑的是让应用程序检查自己的堆栈,以确保它刚输入的方法不在堆栈中。

任何指针都会受到赞赏。

注意:这适用于Web应用程序,但我在多种环境中遇到了这一挑战。

2 个答案:

答案 0 :(得分:3)

您可以像这样检查堆栈:

[MethodImpl(MethodImplOptions.NoInlining)]
// optionally decorate with Conditional to only be used in Debug configuration
[Conditional("DEBUG")]
public static void FailIfCallerIsRecursive() {
    var trace = new StackTrace();
    // previous frame is the caller
    var caller = trace.GetFrame(1).GetMethod();
    // inspect the rest
    for (int i = 2; i < trace.FrameCount; i++) {
        // if found caller somewhere up the stack - throw
        if (trace.GetFrame(i).GetMethod() == caller)
            throw new Exception("Recursion detected");
    }            
}

然后将其称为开头:

void MyPotentiallyRecursiveMethod() {
    FailIfCallerIsRecursive()
}

但请注意,它非常昂贵。但是,因为您将仅在dev(调试)配置中使用它 - 为什么不呢。您还可以稍微修改它以便仅在检测到某种递归级别时抛出(因此调用者在堆栈中显示X时间)。

答案 1 :(得分:1)

如果下一个方法调用会导致(不可捕获)InsufficientExecutionStackException,您可以调用RuntimeHelpers.EnsureSufficientExecutionStack method,然后捕获引发的StackOverflowException

您可以为它创建一个扩展方法:

public static T EnsureSafeRecursiveCall<T>(this Func<T> method)
{
    try
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        return method();
    }
    catch (InsufficientExecutionStackException ex)
    {
        string msg = $"{method.Method.Name} would cause a {nameof(StackOverflowException)} on the next call";
        Debug.Fail(msg);
        // logging here is essential here because Debug.Fail works only with debug
        throw new StackOverflowException(msg, ex); // wrap in new exception to avoid that we get into this catch again and again(note we are in a recursive call)
    }
}

现在你的原始方法几乎保持不变:

public static IEnumerable<T> YourRecursiveMethod<T>(IEnumerable<T> seq)
{
    var method = new Func<IEnumerable<T>>(() => YourRecursiveMethod(seq));
    return method.EnsureSafeRecursiveCall();
}