晚上看到鹤冲天的“反驳老赵之“伪”递归”,大概看了一下,主要是反驳老赵提出的“伪”递归的概念,特别是“伪”,看起来说的都很有道理,但我个人认为,老赵说的没有错,Lambda这种看上去是递归的方式,根本不算是递归。
我引用鹤冲天的递归概念:
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法
我觉得这句话说的很明白,通俗点就是自己调用自己,鹤兄说递归应该不仅仅是过程还是函数,应该包括匿名方法和lambda。我同意匿名方法应该算一种,但因为是匿名方法,我们在开发中无法知道方法名,故我们无法去调用它,但lambda(和委托)算不算一种递归呢?
我们都知道lambda构建的是一个委托,委托只是对一个方法的应用,lambda表达式只是构建了一个匿名方法体,并没有去执行,只有在使用的时候根据需求来延迟加载,但其中是有陷阱的,老赵先前写了一篇“.NET中*延迟*特性的几个陷阱”,其中介绍的非常清楚。那如何反驳鹤兄呢?从他的程序来讲吧。代码:
public static readonly Func<int, int> fac = x => x <= 1 ? 1 : x * fac(x - 1); static void Main(string[] args) { int x = 5; Console.WriteLine(fac(x)); Console.Read(); }
为了看清楚些,我索性执行了,照鹤兄所说,fac调用了fac就应该属于一种递归形式,但你知道它其中执行了什么吗?Look 反编译:
internal class Program { // Fields public static readonly Func<int, int> fac; // Methods static Program() { if (CS$<>9__CachedAnonymousMethodDelegate1 == null) { CS$<>9__CachedAnonymousMethodDelegate1 = new Func<int, int>(null, (IntPtr) <.cctor>b__0); } fac = CS$<>9__CachedAnonymousMethodDelegate1; } private static void Main(string[] args) { Console.WriteLine(fac.Invoke(5)); Console.Read(); } }
这样我们能清楚些,当我们执行委托的时候,会使用Invoke(args)来调用方法体,看清楚,是Invoke方法,并不是委托自己哦,这一点已经偏离了递归的概念了。
再来看下我们原先的递归方法:
static int Fac(int i) { return i <= 1 ? 1 : i * Fac(i - 1); }
反编译的代码:
private static int Fac(int i) { return ((i <= 1) ? 1 : (i * Fac(i))); }
大家看清楚了吧,调用的是它自己哦。
继续说鹤兄的代码,就算鹤兄说委托调用自己委托属于一种递归,但存在着一个“延迟特性的陷阱”,这一点老赵已经说明,每一次调用的是方法体,其中的参数是从外部传进来的,并不是方法自身往下传的,老赵也在“使用Lambda表达式编写递归函数”进行了叙述,什么意思呢?就是我们在委托调用委托的时候,“递归”还没有结束的情况下,如果改变了外部这个参数值,就会影响到“递归”的结果,这也是闭包的一个陷阱。鹤兄用了readonly来让委托只读,想以此来构造一个递归的委托,但真正需要绑定的不是方法体,还需要绑定参数的,你的参数值能通过外部进行改变的,而在传统递归中,第二次调用的时候,参数值都是第一次调用说传入的,外部是无法改变的,这一点是最能说明问题所在的。
总结
太晚了,也不想写太多了,我想懂的人应该明白吧。最后说一下,虽然世界变化的很快,但编程的一些基础还是经的起考验的,并不是说有了匿名方法,委托等就能改变递归的定义,因为从它的诞生之日起,已经有很多人研究过,为什么没有把它定义为委托,肯定有一定道理在里面的。老赵说是一种“伪”递归,这是从代码层面来说的,严格来说,绝对不是递归,我也不是老赵的拥护者,老赵也说了他的SelfApplicable<T, TResult>也不是递归,所以这种驳论觉得没有什么意义。
不过有讨论总归比不讨论要好,我也不知道自己说的对不对,拿出来大家一起讨论,欢迎拍砖。