【整理pcnetman888帖子】奇怪的局部变量:讨论一下C#中的闭包【关于闭包再续】

来源:互联网 发布:淘宝合作平台 编辑:程序博客网 时间:2024/04/29 00:20
pcnetman888】于CSDN-CSDN社区-.NET技术-C# 版 时间: 2009-08-21 13:40:51发的一个帖子中涉及到关于闭包的知识点,我这几天(三年后)才看到,觉的帖子中有许多回复很有价值,并且在他本人的博客中没有看到关于帖子内容以及回复的整理结果,于是我就在没有经过他的同意的情况下私自整理如下(希望pcnetman888本人不要见怪)。

 

帖子原址:奇怪的局部变量

先上帖子中作者的源码:

[0]静态全局字段

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication3{    class Program    {        public static int copy;//[0]这个不是闭包        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                copy = counter;                actions.Add(() => Console.WriteLine(copy));            }            //执行动作            foreach (Action action in actions) action();        }    }}//注:Action定义如下://public delegate void Action();

[1]局部变量(闭包一):

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication3{    class Program    {        static void Main()        {            int copy;//[1]闭包一            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                copy = counter;                actions.Add(() => Console.WriteLine(copy));            }            //执行动作            foreach (Action action in actions) action();        }    }}//注:Action定义如下://public delegate void Action();

[2]局部变量(闭包二):

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication3{    class Program    {        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                int copy;//[1]闭包二                copy = counter;                //int copy = counter;//换种写法                actions.Add(() => Console.WriteLine(copy));            }            //执行动作            foreach (Action action in actions) action();        }    }}//注:Action定义如下://public delegate void Action();

[3]局部变量(闭包三):

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication3{    class Program    {        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)//[3]闭包三            {                actions.Add(() => Console.WriteLine(counter));            }            //执行动作            foreach (Action action in actions) action();        }    }}//注:Action定义如下://public delegate void Action();

 

疑问:

[0]:输出什么?
[1]:输出什么?
[2]:输出什么?
[3]:输出什么?

一下是CSDN个网友关于 问题的回复(仅仅摘录了我觉的有价值的部分,请原帖回复这不要见怪,谢谢):

tddlhl回复:

1和2都只输出一个值9,因为都只有一个变量,不同的是拿的地方不同,一个从堆,一个从栈,3应该输出0到9,因为它从九个变量中拿值,只不过变量的名字一样,但地址是不一样的,4应该输出同样的值,因为只有一个变量

guoyichao回复:

其实就是在匿名方法里引用局部变量的问题,当匿名方法里用到的方法体外的某个局部变量被称作外部变量,按msdn说法编译器会给这些外部变量创建引用防止它们的生命周期过早结束,外部变量和一般的局部变量没什么区别,只不过生命周期是随着使用它的匿名方法结束才结束。
所以判断起来很简单,例子0是静态变量,这个结果大家都知道;例子1虽然是局部变量,但是这个局部变量的作用域在main函数内,每次循环都不会结束它的生命周期,所以结果和例子0一样;而例子2是循环内部的局部变量,每次循环都产生一个新的局部变量,那么编译器每次循环都会创建一个新的引用,所以结果是预期中的;至于3是循环变量,和0、1没差别。

 

pcnetman888(原帖作者)再次回复并贴上了部分代码:

先对比一下:用闭包和没有使用闭包的区别(请注意:C#1x和java是未实现闭包的)

[1]没用闭包的场景:

using System;using System.Collections.Generic;//工具类,其作为是通过"断言"来筛选元素static class ListUtil{    public static IList<T> Filter<T>(IList<T> source, Predicate<T> predicate)    {        List<T> ret = new List<T>();        foreach (T item in source)        {            if (predicate(item))            {                ret.Add(item);            }        }        return ret;    }}//测试类class Test{    static void Main()    {        Console.Write("Maximum length of string to include? ");        int maxLength = int.Parse(Console.ReadLine());        //===============区别部分==================        VariableLengthMatcher matcher = new VariableLengthMatcher(maxLength);        Predicate<string> predicate = new Predicate<string>(matcher.Match);        //===============区别部分==================        string[] words = { "111111111", "222", "33" };        IList<string> shortWords = ListUtil.Filter(words, predicate);    }}// 如果没有闭包,需要增加以下的类(匿名可以缓解这个情况,不过只能在简单场景下,C#1x没有JAVA有)public class VariableLengthMatcher{    int maxLength;    public VariableLengthMatcher(int maxLength)    {        this.maxLength = maxLength;    }    /// <summary>    /// Method used as the action of the delegate    /// </summary>    public bool Match(string item)    {        return item.Length <= maxLength;    }}


[2]使用闭包的场景:

using System;using System.Collections.Generic;//工具类,其作为是通过"断言"来筛选元素static class ListUtil{    public static IList<T> Filter<T>(IList<T> source, Predicate<T> predicate)    {        List<T> ret = new List<T>();        foreach (T item in source)        {            if (predicate(item))            {                ret.Add(item);            }        }        return ret;    }}//测试类class Test{    static void Main()    {        Console.Write("Maximum length of string to include? ");        int maxLength = int.Parse(Console.ReadLine());        //===============区别部分==================        //由于通过闭包,直接使用到上下文,一句话替代了整个VariableLengthMatcher类        Predicate<string> predicate = delegate(string item){return item.Length <= maxLength;};        //===============区别部分==================        string[] words = { "111111111", "222", "33" };        IList<string> shortWords = ListUtil.Filter(words, predicate);    }}


ojlovecd回复:

这几个例子,可以将匿名函数进行转换,这样可以看的更清楚
在[0]中,“外部变量”copy是类的一个静态成员,因此可以讲匿名函数转换为以下形式:

class Program    {        public static int copy;//[0]这个不是闭包        static void TempMethod()        {            Console.WriteLine(copy);        }        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                copy = counter;                actions.Add(new Action(TempMethod));            }            //执行动作            foreach (Action action in actions) action();        }    }


[1],[2]中“外部变量”copy是Main方法中的局部变量,局部变量的生存期现在必须至少延长为匿名函数委托的生存期。这可以通过将局部变量“提升”到编译器生成的类的字段来实现。之后,局部变量的实例化对应于为编译器生成的类创建实例,而访问局部变量则对应于访问编译器生成的类的实例中的字段。而且,匿名函数将会成为编译器生成类的实例方法:

class Program    {        static void Main()        {            //定义动作组            TempClass tc = new TempClass();            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                tc.copy = counter;                actions.Add(tc.TempMethod);            }            //执行动作            foreach (Action action in actions) action();        }        class TempClass        {            public int copy;            public void TempMethod()            {                Console.WriteLine(copy);            }        }    }

 

 class Program    {        static void Main()        {            //定义动作组            //定义动作组            List<Action> actions = new List<Action>();            for (int counter = 0; counter < 10; counter++)            {                TempClass tc = new TempClass();                tc.copy = counter;                actions.Add(tc.TempMethod);            }            //执行动作            foreach (Action action in actions) action();        }        class TempClass        {            public int copy;            public void TempMethod()            {                Console.WriteLine(copy);            }        }    }

[3]中的“外部变量”counter是for循环的循环因子,因此可以转换为以下形式:

class Program    {        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            TempClass tc = new TempClass();            for (tc.copy = 0; tc.copy < 10; tc.copy++)            {                actions.Add(new Action(tc.TempMethod));            }            //执行动作            foreach (Action action in actions) action();        }        class TempClass        {            public int copy;            public void TempMethod()            {                Console.WriteLine(copy);            }        }    }


pcnetman888(原题作者再次更新代码):

把闭包的三种情况组合在一起,
看看,这个怎么转化?

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication3{   class Program   {   static void Main()   {   //定义动作组   List<Action> actions = new List<Action>();   int copyA;   for (int counter = 0; counter < 10; counter++)   {   int copyB;   copyA = copyB = counter;   actions.Add(() => Console.WriteLine(   "copyA:" + copyA.ToString() + "|" +   "copyB:" + copyB.ToString() + "|" +   "counter:" + counter.ToString()));   }   //执行动作   foreach (Action action in actions) action();   }   }}顺例公布一下答案:(分别对应上述三种闭包)copyA:9|copyB:0|counter:10copyA:9|copyB:1|counter:10copyA:9|copyB:2|counter:10copyA:9|copyB:3|counter:10copyA:9|copyB:4|counter:10copyA:9|copyB:5|counter:10copyA:9|copyB:6|counter:10copyA:9|copyB:7|counter:10copyA:9|copyB:8|counter:10copyA:9|copyB:9|counter:10


ojlovecd(对于紧接着上面的代码的回复):

这里有2个不同生命周期的局部变量,将为捕获局部变量的每一个语句块分别创建一个编译器生成的类,这样不同块中的局部变量可以有独立的生存期。TempClassB(对应于内层语句块的编译器生成类)的实例包含局部变量 copyB 和引用 TempClassA 和 TempClass 的实例的字段。TempClassA(对应于外层语句块的编译器生成类)的实例包含局部变量 copyA ,TempClass (对应于for语句块的编译器生成类)的实例包含局部变量counter,使用这些数据结构,可以通过 TempClassB 的实例访问所有被捕获外层变量,匿名函数的代码从而可以实现为该类的实例方法。原则上就是找到生存期最短的局部变量,转换为类,将匿名函数要使用的局部变量转换为类的实例字段,匿名函数的委托方法转换为类的实例方法

 class Program    {        static void Main()        {            //定义动作组            List<Action> actions = new List<Action>();            TempClassA tcA = new TempClassA();            TempClass tc = new TempClass();            for (tc.counter = 0; tc.counter < 10; tc.counter++)            {                TempClassB tcB = new TempClassB();                tcA.copyA = tcB.copyB = tc.counter;                tcB.tcA = tcA;                tcB.tc = tc;                actions.Add(new Action(tcB.TempMethod));            }            //执行动作            foreach (Action action in actions) action();         }        class TempClassA        {            public int copyA;                    }        class TempClassB        {            public int copyB;            public TempClassA tcA;            public TempClass tc;            public void TempMethod()            {                Console.WriteLine(                   "copyA:" + tcA.copyA.ToString() + "|" +                   "copyB:" + copyB.ToString() + "|" +                   "counter:" + tc.counter.ToString());            }        }        class TempClass        {            public int counter;        }    } 


pcnetman888(原帖作者再次给出闭包的理解):

闭包:基本概念
闭包是可以包含自由(未绑定)变量的代码块;
这些变量不是在这个代码块或者任何全局上下文中定义的,
而是在定义代码块的环境中定义。

“闭包” 一词来源于以下两者的结合:
[1]要执行的代码块(由于自由变量的存在,相关变量引用没有释放);
[2]为自由变量提供绑定的计算环境(作用域)。

闭包:分类 
[1]函数闭包
[2]对象闭包
[3]异常闭包

支持闭包的语言:
[1]Scheme
[2]Common Lisp
[3]Smalltalk
[4]Groovy
[5]JavaScript
[6]Ruby
[7]Python

闭包的价值:
[1]函数对象(表示数据)
[2]匿名函数(表示代码)

在C#中,闭包是lamada表达式的基础:
支持闭包的多数语言都将函数作为第一级对象,
就是说这些函数可以存储到变量中、作为参数传递给其他函数,
最重要的是能够被函数动态地创建和返回。

闭包的特点:
闭包为创建和操纵参数化的计算提供了一种紧凑、自然的方式。
可以认为支持闭包就是提供将 “代码块” 作为第一级对象处理的能力:
能够传递、调用和动态创建新的代码块。

语言如何支持闭包:
要完全支持闭包,这种语言必须支持:
[1]可在在运行时:操纵函数
[2]可在在运行时:调用函数
[3]可在在运行时:创建函数
[4]函数可以捕获创建这些函数的环境。
很多语言仅提供了这些特性的一个子集,具备闭包的部分但不是全部优势。
C#是一个完备的闭包支持。

0009给出了一个说法:

这个问题根源在于滞后执行。再举例一个解决方案:

static void Main(string[] args)        {            List<Action> actions = new List<Action>();            Action<int> assign = (i) => actions.Add(() => Console.WriteLine(i));            for (int counter = 0; counter < 10; counter++)            {                assign(counter);            }            foreach (Action action in actions) action();        }


 

原创粉丝点击