Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么应该使用迭代而不是尾部递归?_C#_Design Patterns - Fatal编程技术网

C# 为什么应该使用迭代而不是尾部递归?

C# 为什么应该使用迭代而不是尾部递归?,c#,design-patterns,C#,Design Patterns,什么是设计的味道,递归的糟糕实践? 当我看到resharper提出改进建议时,我很快在谷歌上四处看看。 看到许多关于将尾部递归重新分解为迭代的评论,并将其称为设计气味 public static void DebugOutput2(Exception ex) { if (ex == null) { return; } Debug.WriteLine(ex.Message); if (ex.InnerException != null) {

什么是设计的味道,递归的糟糕实践? 当我看到resharper提出改进建议时,我很快在谷歌上四处看看。 看到许多关于将尾部递归重新分解为迭代的评论,并将其称为设计气味

public static void DebugOutput2(Exception ex) {
    if (ex == null) {
        return;
    }
    Debug.WriteLine(ex.Message);
    if (ex.InnerException != null) {
        DebugOutput2(ex.InnerException);
    }
}

// WAS REFACTORED TO

public static void DebugOutput(Exception ex) {
    if (ex == null) {
        return;
    }
    while (true) {
        Debug.WriteLine(ex.Message);
        if (ex.InnerException != null) {
            ex = ex.InnerException;
            continue;
        }
        break;
    }
}
编辑:基于C#编译器处理注释。看起来它现在是递归的
目标.net 4.5。C#5.0

尾部递归版本的ILDASM输出:显示递归调用而不是迭代

.method public hidebysig static void  DebugOutput(class [mscorlib]System.Exception ex) cil managed
{
  // Code size       54 (0x36)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldnull
  IL_0003:  ceq
  IL_0005:  ldc.i4.0
  IL_0006:  ceq
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  brtrue.s   IL_000e
  IL_000c:  br.s       IL_0035
  IL_000e:  ldarg.0
  IL_000f:  callvirt   instance string [mscorlib]System.Exception::get_Message()
  IL_0014:  call       void [System]System.Diagnostics.Debug::WriteLine(string)
  IL_0019:  nop
  IL_001a:  ldarg.0
  IL_001b:  callvirt   instance class [mscorlib]System.Exception [mscorlib]System.Exception::get_InnerException()
  IL_0020:  ldnull
  IL_0021:  ceq
  IL_0023:  stloc.0
  IL_0024:  ldloc.0
  IL_0025:  brtrue.s   IL_0035
  IL_0027:  nop
  IL_0028:  ldarg.0
  IL_0029:  callvirt   instance class [mscorlib]System.Exception [mscorlib]System.Exception::get_InnerException()
  IL_002e:  call       void ca1.Program::DebugOutput(class [mscorlib]System.Exception)
  IL_0033:  nop
  IL_0034:  nop
  IL_0035:  ret

} // end of method Program::DebugOutput

因为人们错误地关注微观优化而不是清晰易读的代码。

对每个级别(处理的每个对象)进行额外的(递归)函数调用,这也意味着堆栈分配

通常,迭代是因为它没有进行额外的调用/分配


当然,如果有多个对象需要处理,那么差异是显而易见的,我想在您的示例中不是这样。所以对你来说,这不是一个问题,我想目的是教你一个更好的实践。

注意,虽然CLR支持尾部调用(上次我检查时)。在任何情况下,您都不太可能用这个特定的构造破坏堆栈,而且性能也不是一个真正的问题,因为您已经在处理异常了。我将拒绝
while
循环,而选择更接近
while(ex!=null){Debug.WriteLine(ex.Message);ex=ex.InnerException}的循环考虑从性能角度使用尾部递归会发生什么。我们需要创建一个堆栈框架,复制一些内容,然后调用函数。根据递归的不同,这可能会导致堆栈崩溃(虽然在本例中不太可能),而且性能肯定会比非递归迭代差。我认为这与此无关,但一般来说,迭代优于递归,因为它的性能要好得多。ReSharper可能只是建议在可能的情况下使用递归上的迭代,而不考虑是否确实存在性能问题。这样的建议太粗糙了。有很多算法,递归是自然的解决方案,例如,任何树遍历代码都更容易用它来编写。但是,是的,它在32位.NET程序中有cooties,x86抖动不会优化尾部调用。如果递归深度不受实际上限的限制,或者深度超过O(logn),那么你的程序很容易用这个网站的名字来命名。比起节省几纳秒,我更喜欢易于阅读和维护的干式递归解决方案。请注意,凯文强调了一个非常优雅的迭代。一如既往,您可以编写好的和坏的递归。但对我来说,把递归称为一种坏味道似乎有些过头了。尤其是当坚实的设计成果适用时。现在是+1。在接受答案之前,将等待发生什么。感谢ILMTItan,您还可以在递归解决方案中删除InnerException上的冗余空检查。我本以为任何半体面的编译器都会优化尾部递归调用。我知道已经说过了,但是的,使调用尾部递归的全部目的是让编译器优化递归。