tl;博士;
在 C# 中,你是否保证一个惰性迭代器函数只调用它自己并且确实有一个有效的递归退出条件不会导致堆栈溢出?
详细问题:
我知道,作为一项规则,您无法保证 C# 编译器(或 JIT)生成的尾调用优化 (TCO) 指令,因此虽然您可能会获得 TCO,但无法保证。
鉴于对 TCO 的这种认识,我想知道惰性迭代器函数(使用yield return
等)是否因为它们作为协程的性质 - 每个尾调用是否会占用堆栈空间?由于协程的可重入性,我对协程的直觉是,默认情况下每个尾调用都经过优化,因为能够跳出函数并从父框架进入下一个函数而不是创建新框架似乎很自然。
这是 C# 中的行为,还是 C# 迭代器函数的递归调用从当前创建一个新框架,而不是弹出到父框架并使用新参数重新输入?
例子:
public static IEnumerable<IEnumerable<T>> GeneratePermutations<T>(this IEnumerable<T> choices, int numberToChoose)
{
if (numberToChoose == 1)
{
foreach (var choice in choices)
yield return new T[] { choice };
yield break;
}
var subPermutations = choices.SelectMany(choice =>
choices.Where(elem => !EqualityComparer<T>.Default.Equals(elem, choice))
.GeneratePermutations(numberToChoose - 1)
.Select(permutation => (new T[] { choice }).Concat(permutation)));
foreach (var perm in subPermutations)
yield return perm;
}
我的直觉是基于上面的例子subPermutations
只是一个堆计算,它似乎在调用迭代它时,它可以知道它是一个堆计算(它是函数sig的一部分,它是一个迭代器函数),因此立即跳转超出当前帧并将堆积的计算扩展到新帧 -在尝试递归调用之前不会花费额外的堆栈空间......
这种直觉可能完全没有根据……