C#与闭包(closure)学习笔记

(注:来源于《深入理解C#》第三版一书,详细内容请参看此书,代码有所改动,大部分示例代码可以直接运行)
所需背景知识:

  1. 委托:参见MSDN文档delegate (C# Reference)
  2. 闭包:一个函数除了能通过提供给它的参数交互之外,还能同环境进行更大程度的互动。(定义来源于《深入理解C#》第三版)
  3. 外部变量(outer variable) 是指作用域(scope)内包括匿名方法的局部变量或参数(不包括ref和out参数)。在类的实例成员内部的匿名方法中,this引用也被认为是一个外部变量。
  4. 捕获的外部变量(captured outer variable)通常简称为捕获变量(captured variable),它是在匿名方法内部使用的外部变量。重新研究一下“闭包”的定义,其中所说的“函数”是指匿名方法,而与之交互的“环境”是指由这个匿名方法捕获到的变量集。

定义闭包和不同类型的变量

```csharp public delegate void MethodInvoker(); class TestClosure { static void Main(string[] args) { int outerVariable = 5; // 外部变量(未捕获的变量) string capturedVariable = "captured"; // 被匿名方法捕获的外部变量 MethodInvoker x = delegate () { string anonLocal = " local to anonymous method"; // 匿名方法的局部变量 Console.WriteLine(capturedVariable + anonLocal); // 捕获外部变量 }; x(); } } ```

从匿名方法内外访问一个变量
此段代码说明,创建委托实例,不会导致执行

    public delegate void MethodInvoker();
    class TestClosure
    {
        static void Main(string[] args)
        {
            string captured = "before x is created";
            MethodInvoker x = delegate ()
            {
                Console.WriteLine(captured);
                captured = "changed by x";
            };
            captured = "directly before x is invoked";
            x();

            Console.WriteLine(captured);
            captured = "before second invocation";
            x();
        }
    }

输出为:
directly before x is invoked
changed by x
before second invocation

捕获变量的好处

简单地说,捕获变量能简化避免专门创建一些类来存储一个委托需要处理的信息(除了作为 参数传递的信息之外)。 ```csharp List FindAllYoungerThan(List people, int limit) { return people.FindAll(delegate (Person person) // FindAll的参数是一个Predicate委托 { return person.Age < limit; } // 捕获limit ); } ``` 对于c#3.0还能简写为 ```csharp List FindAllYoungerThan(List people, int limit) { return people.Where(person => person.Age < limit); } ```

捕获变量的延长生存期

*对于一个捕获变量,只要还有任何委托实例在引用它,它就会一直存在。*
    public delegate void MethodInvoker();
    class TestClosure
    {
        static void Main(string[] args)
        {
            var x = CreateDelegateInstance();
            x();
            x();
        }

        static MethodInvoker CreateDelegateInstance()
        {
            int counter = 5;

            MethodInvoker ret = delegate
            {
                Console.WriteLine(counter);
                counter++;
            };
            ret();
            return ret;
        }
    }

输出
5
6
7

局部变量实例化

*当一个变量被捕获时,捕捉的是变量的“实例”* ```csharp public delegate void MethodInvoker(); class TestClosure { static void Main(string[] args) { var list = new List();
        for (int index = 0; index < 5; index++)
        {
            int counter = index * 10; // 实例化counter

            list.Add(delegate
            {
                Console.WriteLine(counter);
                counter++;
            });
        }

        foreach (var t in list)
        {
            t();
        }

        list[0]();
        list[0]();
        list[0]();

        list[1]();
    }
}
输出:
0
10
20
30
40
1
2
3
11

<h2 style="background-color:#99FFFF">总结:</h2>
1. 如果用或不用捕获变量时的代码同样简单,那就不要用。
2. 捕获的变量的生存期被延长了,至少和捕捉它的委托一样长。
3. 多个委托可以捕获同一个变量......
4. ......但在循环内部,同一个变量声明实际上会引用不同的变量“实例”。
5. 在for循环的声明中创建的变量1仅在循环持续期间有效——不会在每次循环迭代时都实
例化。这一情况对于C# 5之前的foreach语句也适用。
6. 必要时创建额外的类型来保存捕获变量。
7. 要小心!简单几乎总是比耍小聪明好。

以后在讨论C# 3及其Lambda表达式时,会看到有更多的变量被捕捉。但就目前来说,我们 已经完成了对新的C# 2委托特性的总结。
C#2根本性地改变了委托的创建方式,这样我们就能在.NETFramework的基础上采取一种更 函数化的编程风格。
LINQ促进了闭包的进一步使用,现如今,常见的C#代码中已经越来越多地 使用了闭包。
posted @ 2015-09-28 15:22  jackccc  阅读(1605)  评论(0编辑  收藏  举报