设为首页 加入收藏

TOP

匿名方法中的捕获变量(一)
2019-10-09 19:59:12 】 浏览:106
Tags:匿名 方法 捕获 变量

  乍一接触"匿名方法中的捕获变量"这一术语可能会优点蒙,那什么是"匿名方法中的捕获变量"呢?在章节未开始之前,我们先定义一个委托:public delegate void MethodInvoke();

1、闭包和不同类型的变量:

  首先,大家应该都知道"闭包",它的概念是:一个函数除了能通过提供给它的参数交互之外,还能同环境进行更大程度的互动。但这个定义过于抽象,还需要理解两个术语:

  1)外部变量(outer variable)指作用域内包括匿名方法的局部变量或参数(不包括ref和out参数)。在类的实例成员内部的匿名方法中,this引用也被认为是一个外部变量。

  2)捕获的外部变量(captured outer variable)通常简称捕获变量(captured variable),它是在匿名方法内部使用的外部变量。

  这些定义看起来云里雾里的,那接下来以一个例子来说明: 

 1 public void EnClosingMethod()
 2 {
 3     int outerVariable = 5; // 外部变量
 4     string captureVariable = "captured"; // 被匿名方法捕获的外部变量
 5     if (DateTime.Now.Hour == 23)
 6     {
 7         int normalLocalVariable = DateTime.Now.Minute; // 普通方法的局部变量
 8         Console.WriteLine(normalLocalVariable);
 9     }
10     MethodInvoke x = delegate ()
11     {
12         string anonLocal = "local to anonymous method"; // 匿名方法的局部变量
13         Console.WriteLine(captureVariable + anonLocal); // 捕获外部变量captureVariable
14     };
15     Console.WriteLine(outerVariable);
16     x();
17 }

2、捕获变量的行为:

  如果你运行了上述代码,你会发现匿名方法捕捉到的确实是变量,而不是创建委托实例时该变量的值。通俗的说就是只有在匿名方法被调用时才会被使用。 

 1 string captured = "before x is created";
 2 MethodInvoke x = delegate
 3 {
 4     Console.WriteLine(captured);
 5     captured = "change by x";
 6 };
 7 captured = "directly before x is invoked";
 8 x();
 9 Console.WriteLine(captured);
10 captured = "before second invocation";
11 x();

  上述代码的执行顺序是这样子的(可以debug):定义变量captured => 声明匿名方法MethodInvoke x => 将captured的值修改为"directly before x is invoked" => 紧接着调用委托x(),这个时候会进入匿名方法 => 首先输出captured的值"directly before x is invoked",然后修改为"change by x" => 匿名方法调用结束,来到第9行,输出captured的值"change by x" => 第10行重新给captured赋值"before second invocation" => 调用x()

3、捕获变量到底有什么用处:

  捕获变量能简化避免专门创建一些类来存储一个委托需要处理的信息。

1 List<People> FindAllYoungerThan(List<People> people, int limit)
2 {
3     return people.Where(person => person.Age < limit).ToList();
4 }

  我们在委托实例内部捕获了limit参数——如果仅有匿名方法而没有捕获变量,就只能在匿名方法中使用一个"硬编码"的限制年龄,而不能使用作为参数传递的limit。这样的设计能够准备描述我们的"目的",而不是将大量的精力放在"过程"上。

4、捕获变量的延长生存期:

  到目前为止,我么一直在创建委托实例的方法内部使用委托实例。在这种情况下,你对捕获变量的生存期(lifetime)不会又太大的疑问。但是,假如委托实例"逃"到另一个黑暗的世界(big bad world),那会发生什么?假如创建它的那个方法结束了,它将何以应对?

  在理解这种问题时,最简单的办法就是指定一个规则,给出一个例子,然后思考假如没有那个规则,会发生什么:对于一个捕获变量,只要还有任何委托实例在引用它,它就会一直存在。

 1 private static void Main(string[] args)
 2 {
 3     MethodInvoke x = CreateDelegateInstance();
 4     x();
 5     x();
 6 }
 7 
 8 private static MethodInvoke CreateDelegateInstance()
 9 {
10     int counter = 5;
11 
12     MethodInvoke ret = delegate
13     {
14         Console.WriteLine(counter);
15         counter++;
16     };
17 
18     ret();
19     return ret;
20 }

  输出的结果:

  我们一般认为counter在栈上,所以只要与CreateDelegateInstance对应的栈帧被销毁,counter随之消失,但是从结果来看,显然我们的认知是有问题的。事实上,编译器创建了一个额外的类容纳变量。CreateDelegateInstance方法拥有对该类的一个实例的引用,所以它能使用counter。另外,委托也对该实例的一个引用,这个实例和其他实例一样都在堆上。除非委托准备好垃圾回收,否则那个实例是不会被回收的。

5、局部变量实例化:

  下面将展示一个例子。

1 int single;
2 for (int i = 0; i < 10; i++)
3 {
4     single = 5;
5     Console.WriteLine(single + i);
6 }
1 for (int i = 0; i < 10; i++)
2 {
3     int multiple = 5;
4     Console.WriteLine(multiple + i);
5 }

  上述两段代码在语义和功能上是一样的,但在内存开销上显然第一种写法比第二种占用较小的内存。single变量只实例化一次,而mult

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇wpf source path 下一篇矩阵连乘求解优化

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目