首先感谢园友的指定,后续的文章一定会多码多想,出来的文章才有说服力。那今天接上篇我们来聊一聊匿名函数,对于匿名函数,我们知道使用delegate关键字,那我们来需要知道匿名函数在变量是的处理方式,先说两个术语,外部变量和捕获的外部变量,可以看出"捕获的外部变量=外部变量+捕获了",这个捕获顾名思义就是在匿名函数中使用了这个变量。
外部变量:指在一个包含匿名方法的作用域内的变量或者参数,在类的实例成员内部的匿名方法,this也是认为是一个外部变量。
捕获的外部变量:它是指在匿名方法中使用的外部变量。
代码如下
1 static void Main(string[] args) 2 { 3 //x和y称为外部变量 4 int x = 0, y = 1; 5 //在匿名方法中使用到了x,则x称为捕获的外部变量 6 Action<int> ac = delegate (int n) { Console.WriteLine(x); }; 7 8 //小结:x、y和匿名方法都在Main函数的作用域内,也可以扩展到类的作用域及命名空间的作用域 9 10 Console.ReadKey(); 11 }
再来说下匿名方法捕获变量的行为,可以看到在匿名方法中我们访问到了局部变量x,请注意,并不是仅仅访问到了x的值,而是在匿名类型中使用一个类型实例引用到了变量x,对于x的改变,因为是引用,所以总能使用这个类型实例访问到,如
1 long x1 = 11, y1 = 12; 2 Action<long> ac1 = delegate (long l) { Console.WriteLine(x1); }; 3 ac1(1L); //打印11 4 x1 = y1; 5 ac1(1L); //打印12
参数long l这里没有使用到,不过这里的参数不是上面所说的外部变量,因为它确实是匿名方法的参数
1 static void Debug(int x) { 2 Action<int> a = delegate (int y) { Console.WriteLine(x); }; 3 }
上面的x就是术语中说的外部变量,分清定义就应该没问题了吧。
关于变量的生存周期,可以就只在一个作用域内,当代码执行完这个作用域,该作用域内的变量也会被销毁,但使用匿名方法可以延长变量的生存周期。
1 static void Main(string[] args) 2 { 3 GetLen gl = GetMethod(); 4 gl("s"); //打印00s 5 gl("s"); //打印0000s 6 7 Console.ReadKey(); 8 } 9 10 public delegate int GetLen(string s); 11 static GetLen GetMethod() 12 { 13 string temp = "0"; 14 return delegate (string s) { 15 temp = String.Concat(temp, temp); 16 s = String.Concat(temp, s); 17 Console.WriteLine(s); 18 return s.Length; 19 }; 20 }
看出使用GetMethod返回一个委托,这里使用匿名函数(因为匿名函数就是对应签名的委托),在正常理解下temp在GetMethod作用域内,当离开作用域外,这个变量会销毁,但说过匿名函数会使用一个类型实例引用这个变量,则这个变量不会销毁,只有当匿名函数销毁(也就是委托)才会跟着销毁,从而延长了变量的作用域,而且对于temp变量的操作也会直接反应在实例引用的变量上,如第一次调用gl("s"),temp="00",第二次调用时,temp="0000"。
最后说下有点绕的东西,就是变量的实例化在匿名函数中的访问规则,不过个人感觉这个还真是不很绕,还算是比较好理解的。看下代码。
1 GetLen[] a = { }; 2 int xx = 0; 3 for (int i = 0; i < 3; i++) 4 { 5 int xxx = i; 6 a[i] = delegate (string s) { 7 xx++; 8 xxx++; 9 return xx + xxx; 10 }; 11 }
a是一个委托数组,对于数组中每一个委托都共享一个xx实例引用,而每一个委托都各自拥有一个xxx实例引用(xxx对应不同的委托是不同的),这是因为在循环中,每一次的循环都实例化了xxx,则对于各个委托都有一个全新的xxx实例引用,而xx则是在循环之外实例化的,则每个委托共享一个实例引用。当然在实际的使用过程中,不可能那么简单,那么要我们开动大脑,好好区别哪一个共享的,哪一个是独自引用的。
请斧正。