【发布时间】:2013-08-25 21:28:27
【问题描述】:
什么是设计味道,递归中的不良实践? 一旦我看到 resharper 提出改进建议,我很快就在谷歌上环顾四周。 在将尾递归重构为迭代并将其称为设计气味的过程中,看到了许多 cmets。
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 支持尾调用,但 the C# compiler doesn't emit them(我上次检查过)。在任何情况下,你都不太可能用这个特定的构造来破坏堆栈,而且性能并不是真正的问题,因为你已经在处理异常了。
-
我会拒绝
while循环以支持更接近while(ex != null) {Debug.WriteLine(ex.Message);ex = ex.InnerException}的内容。 -
从性能角度考虑使用尾递归会发生什么。我们需要创建一个堆栈帧,复制一些东西并调用然后调用该函数。根据递归,这可能会导致堆栈崩溃(尽管在此示例中可能不会)并且肯定会导致非递归迭代的性能更差
-
我认为它在这里无关紧要,但总的来说迭代优于递归,因为它的性能要好得多。 ReSharper 可能只是建议尽可能使用迭代而不是递归,而不考虑是否确实存在性能问题。
-
这样的建议太粗鲁了。有很多算法,递归是自然的解决方案,例如,任何树遍历代码都更容易用它编写。但是,是的,它在 32 位 .NET 程序中具有 cooties,x86 抖动不会优化尾调用。如果递归深度不受实际上限的限制或深度大于 O(log n),那么您的程序很可能会使用该网站的名称来字节。
标签: c# design-patterns