C# 非无限递归字符串搜索中的StackOverflowException

C# 非无限递归字符串搜索中的StackOverflowException,c#,tail-recursion,stack-overflow,C#,Tail Recursion,Stack Overflow,背景。 我的脚本在递归搜索大字符串中的特定文本时遇到StackOverflowException。循环不是无限的;问题发生在9000-10000次合法搜索之间(针对特定搜索)——我需要它继续。我正在使用尾部递归(我想),这可能是我的问题的一部分,因为我认为C#做得不好。然而,我不知道如何避免在我的例子中使用尾部递归 问题。为什么会发生StackOverflowException?我的总体方法有意义吗?如果设计糟糕,我宁愿从那里开始,而不是仅仅避免一个异常。但是如果设计是可以接受的,我可以对Sta

背景。 我的脚本在递归搜索大字符串中的特定文本时遇到StackOverflowException。循环不是无限的;问题发生在9000-10000次合法搜索之间(针对特定搜索)——我需要它继续。我正在使用尾部递归(我想),这可能是我的问题的一部分,因为我认为C#做得不好。然而,我不知道如何避免在我的例子中使用尾部递归

问题。为什么会发生StackOverflowException?我的总体方法有意义吗?如果设计糟糕,我宁愿从那里开始,而不是仅仅避免一个异常。但是如果设计是可以接受的,我可以对StackOverflowException做些什么呢

代码。 我编写的类在大量文本(约6MB)中搜索联系人(指定列表中约500多个)。我使用的策略是搜索姓氏,然后在姓氏之前或之后的某个地方查找名字。我需要找到给定文本中每个联系人的每个实例。StringSearcher类有一个递归方法,可以继续搜索联系人,在找到联系人时返回结果,但跟踪搜索结束的位置

我以以下方式使用该类:

StringSearcher searcher = new StringSearcher(
    File.ReadAllText(FilePath),
    "lastname",
    "firstname",
    30
);

string searchResult = null;
while ((searchResult = searcher.NextInstance()) != null)
{
    // do something with each searchResult
}
总的来说,剧本似乎很管用。大多数联系人都会返回我期望的结果。但是,当主搜索字符串非常常见(数千次点击)而次搜索字符串从未出现或很少出现时,问题似乎就会出现。我知道它不会被卡住,因为当前指数正在正常上升

这是我所说的递归方法

public string NextInstance()
{
    // Advance this.CurrentIndex to the next location of the primary search string
    this.SearchForNext();

    // Look a little before and after the primary search string
    this.CurrentContext = this.GetContextAtCurrentIndex();

    // Primary search string found?
    if (this.AnotherInstanceFound)
    {
        // If there is a valid secondary search string, is that found near the
        // primary search string? If not, look for the next instance of the primary
        // search string
        if (!string.IsNullOrEmpty(this.SecondarySearchString) &&
            !this.IsSecondaryFoundInContext())
        {
            return this.NextInstance();
        }
        // 
        else
        {
            return this.CurrentContext;
        }
    }
    // No more instances of the primary search string
    else
    {
        return null;
    }
}
StackOverflowException在
this.CurrentIndex=…
上发生,方法如下:

private void SearchForNext()
{
    // If we've already searched once, 
    // increment the current index before searching further.
    if (0 != this.CurrentIndex)
    {
        this.CurrentIndex++;
        this.NumberOfSearches++;
    }

    this.CurrentIndex = this.Source.IndexOf(
            this.PrimarySearchString,
            ValidIndex(this.CurrentIndex),
            StringComparison.OrdinalIgnoreCase
    );

    this.AnotherInstanceFound = !(this.CurrentIndex >= 0) ? false : true;
}
如果需要,我可以包含更多代码。如果这些方法或变量之一有问题,请告诉我


*性能并不是一个真正的问题,因为这可能会在夜间作为计划任务运行。

您有一个1MB堆栈。当堆栈空间用完并且您仍然需要更多的堆栈空间时,将抛出一个
StackOverflowException
。这可能是也可能不是无限递归的结果,运行时不知道。无限递归只是使用更多可用堆栈空间的一种有效方法(通过使用无限量)。你可以使用一个有限的数量,刚好超过可用的数量,你会得到同样的例外

虽然还有其他方法可以消耗大量堆栈空间,但递归是最有效的方法之一。每个方法都基于该方法的签名和局部变量添加了更多的空间。深度递归可能会占用大量的堆栈空间,因此,如果您希望深度超过几百层(甚至很多),那么您可能不应该使用递归。请注意,任何使用递归的代码都可以迭代编写,或者使用显式的
堆栈

很难说,因为没有显示完整的实现,但根据我看到的情况,您或多或少都在编写迭代器,但没有使用C#构造(即
IEnumerable

我猜“迭代器块”将使这个算法更容易编写,更容易非递归地编写,并且从调用方的角度来说更有效

下面从较高的层次介绍如何将此方法构造为迭代器块:

public static IEnumerable<string> SearchString(string text
    , string firstString, string secondString, int unknown)
{
    int lastIndexFound = text.IndexOf(firstString);

    while (lastIndexFound >= 0)
    {
        if (secondStringNearFirst(text, firstString, secondString, lastIndexFound))
        {
            yield return lastIndexFound.ToString();
        }
    }
}

private static bool secondStringNearFirst(string text
    , string firstString, string secondString, int lastIndexFound)
{
    throw new NotImplementedException();
}
公共静态IEnumerable搜索字符串(字符串文本
,字符串firstString,字符串secondString,int未知)
{
int lastIndexFound=text.IndexOf(第一个字符串);
而(lastIndexFound>=0)
{
if(secondStringNearFirst(text、firstString、secondString、lastIndexFound))
{
收益率返回lastIndexFound.ToString();
}
}
}
私有静态bool secondStringNearFirst(字符串文本
,字符串firstString,字符串secondString,int lastIndexFound)
{
抛出新的NotImplementedException();
}

这里递归似乎不是正确的解决方案。通常,对于递归问题,您会将某些状态传递给递归步骤。在这种情况下,您实际上有一个普通的
while
循环。下面我将您的方法体放入一个循环中,并将递归步骤更改为
continue
。看看这是否有效

public string NextInstance()
{
    while (true)
    {
        // Advance this.CurrentIndex to the next location of the primary search string
        this.SearchForNext();

        // Look a little before and after the primary search string
        this.CurrentContext = this.GetContextAtCurrentIndex();

        // Primary search string found?
        if (this.AnotherInstanceFound)
        {
            // If there is a valid secondary search string, is that found near the
            // primary search string? If not, look for the next instance of the primary
            // search string
            if (!string.IsNullOrEmpty(this.SecondarySearchString) &&
                !this.IsSecondaryFoundInContext())
            {
                continue; // Start searching again...
            }
            // 
            else
            {
                return this.CurrentContext;
            }
        }
        // No more instances of the primary search string
        else
        {
            return null;
        }
    }
}

+1就像一个符咒。感谢您对何时使用递归的建议。+1感谢您对异常发生原因的解释。我认为使用您建议的迭代器是最好的设计方法。