一本好书,或是一本比较有深度的书,就是每次研读的时候都会有新的发现。

好吧,我承认每次读的时候都有泛泛而过的嫌疑~~

这几年一直专注于C#客户端的开发,逐步从迷迷糊糊,到一知半解,再到自以为是,最后沉下心重新审视。也许这也是一种进步一种自我学习的过程。

前面啰嗦了这么多,希望大家也能不那么浮躁的“深入理解”C#这门语言的每个知识点。本文总结书本中的知识,在结合实际应用场合进行概述,如果有不正确的地方,还请不吝指教。

文章中的内容比较浅显,请高手略过此文。

4. 程序闭包

程序闭包的问题是由于程序对某些变量进行了预判和处理(个人理解,若有误或不足请指正)使得某些变量理应作为值类型却变为了引用类型导致数据异常。

当然,大多数情况下,我们是不会遇到这样的问题,但在某些情况下,我们不得不注意并分析问题的根本原因,BUG永远不是随机的。

下面通过几个例子逐步来理解闭包的概念:

例4.1

  1. private void Button1_Click(object sender, RoutedEventArgs e)
  2. {
  3. int outerVariableCaptured = ; // 外部变量(被捕获)
  4. int outerVariableUnCaptured = ; // 外部变量(未捕获)
  5.  
  6. if (DateTime.Now.Hour <= )
  7. {
  8. int normalLocalVariable = ; // 普通方法的局部变量,不是外部变量,因为在其作用域内无匿名方法。
  9. this.Txb_Msg.Text += string.Format("普通方法的局部变量 = {0}", normalLocalVariable) + System.Environment.NewLine;
  10. }
  11.  
  12. Action x = new Action(() =>
  13. {
  14. int anonLocal = ; // 匿名方法的局部变量
  15. this.Txb_Msg.Text += string.Format("匿名方法的局部变量 = {0}", anonLocal) + System.Environment.NewLine;
  16. this.Txb_Msg.Text += string.Format("匿名方法中被捕获的外部变量 = {0}", outerVariableCaptured)
  17. + System.Environment.NewLine; // 匿名方法中调用了作用域外的变量,所以变量变为被捕获的外部变量
  18. });
  19.  
  20. this.Txb_Msg.Text += string.Format("普通方法的未捕获的外部变量 = {0}", outerVariableUnCaptured) + System.Environment.NewLine;
  21.  
  22. x();
  23. }
  1. 输出结果:
  2. 普通方法的局部变量 =
  3. 普通方法的未捕获的外部变量 =
  4. 匿名方法的局部变量 =
  5. 匿名方法中被捕获的外部变量 =

4.1中是让大家了解外部变量,局部变量,捕获等相关概念。其中最重要的是被捕获的外部变量。

例4.2

  1. private void Button3_Click(object sender, RoutedEventArgs e)
  2. {
  3. // 证明被捕捉的局部变量声明周期被延长了。
  4. OnCreateDelegate += MainWindow_OnCreateDelegate;
  5.  
  6. this.Dispatcher.Invoke(OnCreateDelegate(this)); // 此处Invoke容易引起歧义:原因在于Invoke事件之后返回的还是一个事件。
  7. // Counter是值类型,逃脱其作用域时栈上数据会被回收,真实的情况是这样吗?
  8. // 从另一个侧面也说明了,值类型是在栈上还是堆上,依赖于创建对象的类型。
  9. }
  1. private Delegate MainWindow_OnCreateDelegate(object sender)
  2. {
  3. var frm = sender as MainWindow;
  4. int Counter = ;
  5. var a = new Action(() =>
  6. {
  7. frm.Txb_Msg.Text += string.Format("委托内部的变量值 = {0}", Counter) + System.Environment.NewLine;
  8. Counter++;
  9. });
  10. a();
  11.  
  12. return a;
  13. }
  1. 输出:
  2. 委托内部的变量值 =
  3. 委托内部的变量值 =

这个例子要说明的是Counter其值类型原本的生存周期应该在MainWindow_OnCreateDelegate(object sender)方法中,可是偏偏却逃离了方法的作用域,这就是我们所说的值类型是在堆上Or栈上
完全取决于其初始化的位置是在栈上还是在堆上。

例4.3

  1. private void Button4_Click(object sender, RoutedEventArgs e)
  2. {
  3. // 更复杂的一些情况
  4. var methods = new Action[];
  5. int outside = ; // 实例化变量一次
  6. for (int i = ; i < ; i++)
  7. {
  8. int inside = ; // 实例化变量多次
  9. methods[i] = new Action(() =>
  10. {
  11. this.Txb_Msg.Text += string.Format("Inside Value = {0}; Outside Value = {1} ", inside, outside) + System.Environment.NewLine;
  12. inside++;
  13. outside++; // 匿名方法捕获的变量
  14. });
  15. }
  16.  
  17. methods[].Invoke();
  18. methods[].Invoke();
  19. methods[].Invoke();
  20. methods[].Invoke();
  21.  
  22. }
  1. 输出结果:
  2. /***************Outside变量内存共享*************
  3. * Inside Value = 100; Outside Value = 10
  4. * Inside Value = 101; Outside Value = 11
  5. * Inside Value = 102; Outside Value = 12
  6. * Inside Value = 100; Outside Value = 13
  7. * *******************************************/

这个例子就得好好想想了,outside和inside的值到底会是什么?为什么会这样?
原因在于inside在For循环的内部初始化了多次,也就是说For循环几次,就有几个独立的inside对象,虽说它是值类型。

例4.4

这个例子摘自《编写高质量代码:改善C#程序的157个建议》

  1. private void Button5_Click(object sender, RoutedEventArgs e)
  2. {
  3. // 闭包陷阱
  4. var methods = new Action[];
  5. for (int i = ; i < ; i++)
  6. {
  7. int inside = i; // 实例化变量多次
  8. methods[i] = new Action(() =>
  9. {
  10. this.Txb_Msg.Text += string.Format("Inside Value = {0}; Index Value = {1} ", inside, i) + System.Environment.NewLine;
  11. });
  12. }
  13. methods[].Invoke();
  14. methods[].Invoke();
  15.  
  16. /*****************闭包陷阱*********************
  17. * 当使用i的值时,i就是前面说的共享变量(捕获的外部变量),所以总是输出i的最大值。
  18. * 当使用Inside值时,Inside就是内部变量,每次创建对象都重新生成,所以此处inside的值是递增,即缓存下i的值。
  19. * 对于IL,其创建了Tempclass.i来代替i,导致了i值共享。
  20. * *******************************************/
  21. }
  1. 输出结果:
  2. Inside Value = ; Index Value =
  3. Inside Value = ; Index Value =

其实如果用ILDasm来看的话,针对i这个对象,IL生成了一个DisplayClass(就是一个名字而已)这样一个类,最总导致了,i变为引用类型,数据异常。

持续更新:示例代码下载

温故知新---重读C#InDepth(二)的更多相关文章

  1. 温故知新---重读C#InDepth(一)

    一本好书,或是一本比较有深度的书,就是每次研读的时候都会有新的发现. 好吧,我承认每次读的时候都有泛泛而过的嫌疑~~ 这几年一直专注于C#客户端的开发,逐步从迷迷糊糊,到一知半解,再到自以为是,最后沉 ...

  2. ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘

    开篇:经历了上一篇<aspx与服务器控件探秘>后,我们了解了aspx和服务器控件背后的故事.这篇我们开始走进WebForm状态保持的一大法宝—ViewState,对其刨根究底一下.然后,再 ...

  3. 温故知新——Spring AOP(二)

    上一篇我们介绍Spring AOP的注解的配置,也叫做Java Config.今天我们看看比较传统的xml的方式如何配置AOP.整体的场景我们还是用原来的,"我穿上跑鞋",&quo ...

  4. Android——开源框架Universal-Image-Loader + Fragment使用+轮播广告

    原文地址: Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用 Android 开源框架Universal-Image-Loader完全解析(二) ...

  5. 【温故知新】——BABYLON.js学习之路·前辈经验(二)

    前言:在上一篇随笔BABYLON.js学习之路·前辈经验(一)中回顾了组内同事们长时间在Babylon开发实践中的总结出的学习之路和经验,这一篇主要对开发中常见的一些功能点做一个梳理,这里只作为温故知 ...

  6. 再回首,Java温故知新(二):Java基本数据类型

    Java作为一种强类型语言,意味着每一个变量都会有特定的类型,Java共有8种基本类型,其中有4种整型(byte.short.int.long).两种浮点型(float.double).1种字符型(c ...

  7. 重读《深入理解Java虚拟机》二、Java如何分配和回收内存?Java垃圾收集器如何工作?

    线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行 ...

  8. 【温故知新】Java web 开发(二)Servlet 和 简单JSP

    系列一介绍了新建一个 web 项目的基本步骤,系列二就准备介绍下基本的 jsp 和  servlet 使用. (关于jsp的编译指令.动作指令.内置对象不在本文讨论范围之内) 1. 首先,在 pom. ...

  9. 深入js的面向对象学习篇(封装是一门技术和艺术)——温故知新(二)

    下面全面介绍封装和信息隐藏. 通过将一个方法或属性声明为私用的,可以让对象的实现细节对其它对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束.在代码有许多人参与设计的情况下, ...

随机推荐

  1. kvm解决1000M网卡问题

    1.当我们安装完虚拟机, 发现虚拟机竟然是 100M 网络, 传输速率很低, 那是怎么导致的呢,如何来解决呢? 需要我们修改 vm01.xml 配置文件网卡段,添加如下红色标记行,改 为 e1000, ...

  2. 关于 Python 你需要知道的几个概念

    Python 一种支持面向对象和函数式(面向过程)的高级编程语言 CPython 由 C 语言编译,一种默认的,通常我们所提及的基于 C 的 Python 的一种实现 Cython 一种 Python ...

  3. iOS使用Core Graphics和UIBezierPath绘画

    通过UIView的子类的- (void)drawRect:(CGRect)rect 函数可用对视图进行重新绘画: 要重新绘画可以通过Core Graphics和UIBezierPath来实现. 1.通 ...

  4. NUL 与 NULL

    NUL 与 NULL 在C语言中,字符串表示为字符的数组.字符串最后一个字符为空字符 ('\0'),官方将其定义为 NUL ,而 NULL 是一个宏,其定义如下: #define NULL ((voi ...

  5. POJ 2891 Strange Way to Express Integers【扩展欧几里德】【模线性方程组】

    求解方程组 X%m1=r1 X%m2=r2 .... X%mn=rn 首先看下两个式子的情况 X%m1=r1 X%m2=r2 联立可得 m1*x+m2*y=r2-r1 用ex_gcd求得一个特解x' ...

  6. Unity CombineChildren和MeshCombineUtility

    原理 Unity3D如何通过CombineChildren和MeshCombineUtility优化场景? 首先解释下联结的原理和意思:文档里说,显卡对于一个含100个面片的物体的和含1500个面片的 ...

  7. 使用CuteSlider做网站炫酷的幻灯片

    cuteSlider 1.预览 官网:http://www.cuteslider.com/ 应用:http://www.dutphonelab.org/ 2.资料 文档:http://pan.baid ...

  8. POJ 1088滑雪

    滑雪 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 89168   Accepted: 33474 Description ...

  9. android gravity属性 和 weight属性

    来看这个布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:and ...

  10. (转载)关于Apache 的两种工作模式

    今天在查看服务器的时候,发现服务器http请求数 每天增长越来越多,在优化集群服务器的时候,查看到Apache 的工作模式是prefork,于是想到了worker 模式, 想暂时的把当前运行模式改成w ...