C# 简单的尾部递归函数和循环一样有效吗?

C# 简单的尾部递归函数和循环一样有效吗?,c#,clr,C#,Clr,有时我发现自己正在编写尾部递归函数。我一直在到处寻找,我发现在.NET框架中有尾部递归,但我不确定在什么情况下我可以,在什么情况下我不能有效地使用尾部递归。例如,我有一个简单的树,让我们称之为 public class Tree { public Tree parent = null; public Tree(Tree parent) { this.parent = parent; this.children = new List<Tree>();

有时我发现自己正在编写尾部递归函数。我一直在到处寻找,我发现在.NET框架中有尾部递归,但我不确定在什么情况下我可以,在什么情况下我不能有效地使用尾部递归。例如,我有一个简单的树,让我们称之为

public class Tree
{
  public Tree parent = null;

  public Tree(Tree parent)
  {
    this.parent = parent;
    this.children = new List<Tree>();
  }

  public List<Tree> children {get; private set;}

  public Tree root
  {
    get
    {
      return this.parent == null ? this : parent.root;
    }
  }
}
公共类树
{
公共树父级=null;
公共树(树父级)
{
this.parent=parent;
this.children=新列表();
}
公共列表子项{get;private set;}
公有树根
{
得到
{
返回this.parent==null?this:parent.root;
}
}
}
对于root属性,编译器会发出循环吗?它会发出尾巴吗?抖动会尊重尾巴吗?难道什么都不会发生,算法会递归运行吗?最重要的是,我应该将其重写为迭代吗?

C#编译器永远不会发出
tail
前缀

如果呼叫处于尾部位置,F#将这样做。尾部递归是否适用取决于遍历树的顺序

在您的代码中,尾部位置没有任何内容。原因是使用了三元运算符。如果代码被重写为使用
If
语句,并且每个分支都返回,那么对
parent.root
的调用将处于尾部位置

在优化方面,编译器(F#或IronScheme)通常会将尾部递归调用转换为
while(true){…}
循环。这样做是因为它消除了尾部调用和再次调用该方法的需要

因此,如果C#被允许发出尾部调用(事实并非如此),它很可能会被转换为:

public Tree root
{
  get
  {
    if (parent == null) return this;
    else return parent.root; // now in tail position
  }
}
(只是一个客人)

F#和IronScheme都进行相同的转换。这称为
尾部呼叫消除
(TCE)。是的,它将比C版本稍微快一点。(我已经在C#、F#和IronScheme上通过微标记
fib
测试了这一点)

也有类似的答案


关于速度。尾部递归优化和小函数的循环并没有什么不同。当启动尾部调用优化时,只需将“call”指令(在x86上)替换为“jmp”。当执行相同的贯穿循环时,您将有相同的“jmp”指令来进入下一个循环。您应该记住的一点是,整个函数体将是循环体,因此您应该尽量减小递归函数的大小。

好吧,我的问题是:您测量了吗?要获得对该问题的良好投票,请附上您的一些研究成果。你能给你提到的那些案例写信并计时吗?我希望有人知道答案。我自己对反汇编程序并不熟悉,因为这个具体案例取决于调试或发布版本控制,我不知道这会如何影响JIT或字节码,如果这是一个JIT效应,那么很难有效地测量,我担心如果我尝试测量它,我可能得出了错误的结论。你能添加一些文档参考吗?它被添加到C#中,因此我可以得出结论:“没有尾部调用消除,手动循环以避免堆栈空间耗尽(如果有,我的示例中的三元运算符会破坏它)”?这已经足够接近实际目的了。是的,C#编译器从不发出尾部前缀,但是,抖动实现方法即使没有前缀也可以进行尾部递归是合法的。x86 32位抖动从来不会这样做,但64位抖动偶尔会使C方法尾部递归,这有点令人惊讶。我很想知道这一点,我在这一点上打开了一个uservoice:注意:它不是称为
尾部调用优化
,而是称为
尾部调用消除
:)@leppie,我不认为这个术语唯一适用(参见)。我从和
mono--optimize=tailc
中了解到这一时刻。使用该规则,您可以调用
循环倒带优化
,作为
循环内显式cmp消除
;)维基上说两者都是一样的。。。TCO听起来在功能和语义上都是错误的。
public Tree root
{
  get
  {
    Tree temp = this;
    while (true)
    {
      if (temp.parent == null)
      {
         return temp;
      }
      temp = temp.parent;
    }
  }
}