C# 有没有一种方法可以阻止或断言c中的意外递归

C# 有没有一种方法可以阻止或断言c中的意外递归,c#,recursion,C#,Recursion,我正在处理一个大型而复杂的事件驱动的代码体,有大量的机会意外地创建递归条件 有时,递归条件是临时的,应用程序会赶上自己,但即使这样,通常也会造成不必要的延迟。其他时候,它会创建一个stackoverflow,当它发生在客户端站点时,通常很难进行调试 我希望有一种方法可以将允许递归的代码部分列入黑名单或白名单。如果递归条件在开发过程中发生,那么我希望它被断言,以便我可以更正代码 我考虑的是让应用程序检查它自己的堆栈,以确保它刚刚输入的方法不在堆栈上 任何指点都将不胜感激 注意:这是针对Web应用程

我正在处理一个大型而复杂的事件驱动的代码体,有大量的机会意外地创建递归条件

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

我希望有一种方法可以将允许递归的代码部分列入黑名单或白名单。如果递归条件在开发过程中发生,那么我希望它被断言,以便我可以更正代码

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

任何指点都将不胜感激


注意:这是针对Web应用程序的,但我在多个环境中遇到了这个挑战。

您可以像这样检查堆栈:

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

但请注意,它相当昂贵。然而,既然您只打算在dev调试配置中使用它,为什么不呢。您还可以稍微修改它,使其仅在检测到特定级别的递归时抛出,以便调用方在堆栈上出现X次。

您可以像这样检查堆栈:

[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");
    }            
}
但请注意,它相当昂贵。然而,既然您只打算在dev调试配置中使用它,为什么不呢。您还可以稍微修改它,使其仅在检测到特定级别的递归时抛出,以便调用方在堆栈上X次出现。

如果下一个方法调用将导致不可捕获的StackOverflowException,则可以调用,然后捕获引发的InsufficientExecutionStackException

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

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();
}
如果下一个方法调用将导致不可捕获的StackOverflowException,则可以调用,然后捕获引发的InsufficientExecutionStackException

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

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

定义意外递归。有些算法依赖于快尾递归,甚至不会引发堆栈溢出。是的,……不要使用应用程序。难道你应该重新考虑设计吗?创建ReactiveX和observables是为了处理事件流,并允许您在事件流上使用查询逻辑、组合事件流等。代理架构允许您发送消息,并可能附加TTL字段以防止出现无限错误loops@PanagiotisKanavos,我没有预料到的递归/设计。A调用B调用C在循环中调用A,而我期望A在没有回叫自己的情况下解决问题。性能不是问题吗?因为探索堆栈非常昂贵。定义意外递归。有些算法依赖于快尾递归,甚至不会引发堆栈溢出。是的,……不要使用应用程序。难道你应该重新考虑设计吗?创建ReactiveX和observables是为了处理事件流,并允许您在事件流上使用查询逻辑、组合事件流等。代理架构允许您发送消息,并可能附加TTL字段以防止出现无限错误loops@PanagiotisKanavos,我没有预料到的递归/设计。A调用B调用C在循环中调用A,而我期望A在没有回叫自己的情况下解决问题。性能不是问题吗?因为探索堆栈是非常昂贵的事情。这似乎只适用于Debug.Fail,而Debug.Fail并不总是有用的。如果您尝试执行任何其他操作,例如Console.WriteLine-似乎仍然会导致堆栈溢出。@Evk:据我所知,这是OP的要求。但我无法重现您所说的内容。没关系,您不需要用throw重新显示exception,因为每个YourRecursiveMethod都有这个特定类型的catch子句,所以每个catch块都会在堆栈上被多次触发。当然,最好抛出另一个异常,而不是用Debug重新抛出。失败并不重要。@Evk:was只是为了防止编译器出错。方法必须返回一些东西。您也可以在扩展版本EnsuresPerfereCursiveCall中返回defaultTin。是的,我理解,正如我在Debug中所说的。失败并不重要,但如果您想执行不同的写入日志或其他操作,最好不要重新执行它,而是抛出不同的异常。这似乎只适用于Debug.Fail,它并不总是有用的。如果您尝试执行任何其他操作,例如Console.WriteLine-似乎仍然会导致堆栈溢出。@Evk:据我所知,这是OP的要求。但我无法重现您所说的内容。没关系,您不需要用throw重新显示exception,因为每个YourRecursiveMethod都有这个特定类型的catch子句,所以每个catch块都会在堆栈上被多次触发。最好再抛出一些例外
n而不是用Debug.Fail重新调用。这没关系。@Evk:was只是为了防止编译器出错。方法必须返回一些东西。您也可以在扩展版本EnsuresPerfereCursiveCall中返回defaultTin。是的,我理解,正如我在Debug中所说的。失败并不重要,但如果您想执行不同的写入日志或其他操作,最好不要重试,而是抛出不同的异常。非常好,谢谢。我将我的版本更改为抛出以下消息:抛出在+调用者中检测到的新异常未预期递归。Name@AnthonyVO我还更新了答案并用[Conditional]属性标记。这样,当不在调试配置中时,对该方法的所有调用都不会被编译,因此在发布版本中,它将在不进行任何IF调试检查的情况下产生0效果。非常有效,谢谢。我将我的版本更改为抛出以下消息:抛出在+调用者中检测到的新异常未预期递归。Name@AnthonyVO我还更新了答案并用[Conditional]属性标记。这样,当不在调试配置中时,对该方法的所有调用都不会被编译,所以在发布版本中,它将在不进行任何IF调试检查的情况下产生0效果。