首页 > 学院 > 开发设计 > 正文

温故知新---重读C#InDepth(二)

2019-11-17 03:01:04
字体:
来源:转载
供稿:网友

温故知新---重读C#InDepth(二)

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

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

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

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

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

4. 程序闭包

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

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

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

例4.1

        PRivate void Button1_Click(object sender, RoutedEventArgs e)        {            int outerVariableCaptured = 5; // 外部变量(被捕获)            int outerVariableUnCaptured = 50; // 外部变量(未捕获)            if (DateTime.Now.Hour <= 24)            {                int normalLocalVariable = 1; // 普通方法的局部变量,不是外部变量,因为在其作用域内无匿名方法。                this.Txb_Msg.Text += string.Format("普通方法的局部变量 = {0}", normalLocalVariable) + System.Environment.NewLine;            }            Action x = new Action(() =>            {                int anonLocal = 2; // 匿名方法的局部变量                this.Txb_Msg.Text += string.Format("匿名方法的局部变量 = {0}", anonLocal) + System.Environment.NewLine;                this.Txb_Msg.Text += string.Format("匿名方法中被捕获的外部变量 = {0}", outerVariableCaptured)                    + System.Environment.NewLine; // 匿名方法中调用了作用域外的变量,所以变量变为被捕获的外部变量            });            this.Txb_Msg.Text += string.Format("普通方法的未捕获的外部变量 = {0}", outerVariableUnCaptured) + System.Environment.NewLine;            x();        }
输出结果:普通方法的局部变量 = 1普通方法的未捕获的外部变量 = 50匿名方法的局部变量 = 2匿名方法中被捕获的外部变量 = 5

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

例4.2

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

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

例4.3

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

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

例4.4

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

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

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

持续更新:示例代码下载


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表