[点滴系列][1]:从闭包说起
世界杯车轮战开始了,连通三天,基本进入世界杯状态。看球也不能忘了玩技术,这次打算把接触c#以来的点滴总结起来,让原本模糊的概念清晰起来,博友们一起来吧!
[闭包]接触这个词的第一感觉就是晦涩难懂,下面我们就来啃一啃。
一、邂逅[闭包]
第一次接触闭包是在js里,先来看代码段[1]:
1 function a() { 2 var i = 0; 3 function b() { 4 alert(++i); 5 } 6 return b; 7 } 8 var c = a(); 9 c();
很简单的代码,细心观察会发现变量i的作用域是在方法a中,也就是说出了方法a后变量i就不起作用了,可代码中的i依然活跃在方法c中,是不是违背了程序的基本规则呢?球出界了队员还在踢。
二、直面[闭包]
先来啃啃[闭包]的正经概念,[闭包(Closure)]就是引用了自由变量的表达式,通常是函数(也就是代码段[1]中的函数b)。它的特点是被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外,通俗的讲就是大家常说的闭包延长了变量的生命期。对照代码段[1],清晰些了吗?
下面来看c#版的,代码段[2]:
1 public class Program 2 { 3 public static Action funcB() 4 { 5 Console.WriteLine("funcB Begin.."); 6 int i = 0; 7 i++; 8 Console.WriteLine("funcB:" + i); 9 Action action = () => 10 { 11 Console.WriteLine("funcA:" + i); 12 }; 13 i = 100; 14 Console.WriteLine("funcB:" + i); 15 Console.WriteLine("funcB End.."); 16 return action; 17 } 18 static void Main() 19 { 20 var action = funcB(); 21 action(); 22 Console.ReadKey(); 23 } 24 }
1 public class Program 2 { 3 public static async Task funcA(Action callback) 4 { 5 //停留5秒,缓下节奏 6 await Task.Delay(5000); 7 Console.WriteLine("funcA continue.."); 8 callback(); 9 } 10 public static void funcB() 11 { 12 Console.WriteLine("funcB Begin.."); 13 int i = 0; 14 i++; 15 Console.WriteLine("funcB:" + i); 16 funcA(() => 17 { 18 Console.WriteLine("funcA:" + i); 19 }); 20 i = 100; 21 Console.WriteLine("funcB:" + i); 22 } 23 static void Main() 24 { 25 funcB(); 26 Console.WriteLine("funcB End.."); 27 Console.ReadKey(); 28 } 29 }
两个写法目的一样,就是想勾起大家的所有疑问。代码段[2]的运行结果是什么呢?为什么是这样的结果呢?[闭包]真的延长了变量的生命期吗?相信熟悉的人是清楚的,我们要做的是继续深挖。
三、深挖[闭包]
我们都懂这只是语法糖,它并没有违背程序的基本规律。下面就抄家伙(.NET Reflector)来窥窥究竟。
图[1] 图[2]
图[3]
图[4]
图[5]
一下上了5张图,不要慌,慢慢来。
图[1]是Reflector中Program类的字段、方法、类等的名称列表。我们注意到,除去我们代码中定义的,编译器还自动生成了一个类:c__DisplayClass1 !!
图[2]是编译器自动生成的类c__DisplayClass1中的一个方法<funcB>b__0的定义,其实就是funcB方法中的那个匿名函数 ()=>{Console.WriteLine("funcA:" + i);} 的实现;
图[3]是编译器自动生成的类c__DisplayClass1的实现的IL代码,请注意方法<funcB>b__0和公共变量i
图[4]、图[5]是funB方法的IL代码,每一段代表的啥意思我都大概做了标注。可以看到:在方法的一开始,编译器就初始化了c__DisplayClass1类的一个实例,之后对于变量i的操作,在IL中其实就是对于起初初始化的那个全局的c__DisplayClass1类实例中的变量i的操作,所以说[闭包]延长了变量的生命期是假象,其实我们一直在操作一个全局的类实例的变量。
四、模仿[闭包]
原理基本清楚了,下面我们来自己动手模仿一下编译器做的事。
代码段[3]:
1 public class Program 2 { 3 //定义一个全局的c__DisplayClass1类型的变量。 4 static c__DisplayClass1 displayCls; 5 /// <summary> 6 /// 这就是类似于编译器的那个自定义类<>c__DisplayClass1 7 /// </summary> 8 sealed class c__DisplayClass1 9 { 10 public int i; 11 12 public void b_0() 13 { 14 Console.WriteLine("funcA:" + i); 15 } 16 } 17 public static Action funcB() 18 { 19 displayCls = new c__DisplayClass1(); 20 Console.WriteLine("funcB Begin.."); 21 displayCls.i = 0; 22 displayCls.i++; 23 Console.WriteLine("funcB:" + displayCls.i); 24 Action action = displayCls.b_0; 25 displayCls.i = 100; 26 Console.WriteLine("funcB:" + displayCls.i); 27 Console.WriteLine("funcB End.."); 28 return action; 29 } 30 static void Main() 31 { 32 var action = funcB(); 33 action(); 34 Console.ReadKey(); 35 } 36 }
编译器费尽心思给我们做了一个语法糖,让我们的编程更加轻松优雅。
五、终极想象
只上代码,代码段[4]:
1 public class Program 2 { 3 public static List<Action> funcB() 4 { 5 List<Action> list = new List<Action>(); 6 Console.WriteLine("funcB Begin.."); 7 int i = 0; 8 i++; 9 Console.WriteLine("funcB:" + i); 10 Action action1 = () => 11 { 12 Console.WriteLine("funcA:" + i); 13 i = 200; 14 }; 15 Action action2 = () => 16 { 17 Console.WriteLine("funcA:" + i); 18 }; 19 i = 100; 20 Console.WriteLine("funcB:" + i); 21 Console.WriteLine("funcB End.."); 22 list.Add(action1); 23 list.Add(action2); 24 return list; 25 } 26 static void Main() 27 { 28 var action = funcB(); 29 action[0](); 30 action[1](); 31 Console.ReadKey(); 32 } 33 }
运行结果是什么呢?自己动手,丰衣足食。
就到这儿吧,有问题的地方希望各位指正,敬礼!