zoukankan      html  css  js  c++  java
  • 温故知新---重读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变为引用类型,数据异常。

    持续更新:示例代码下载

  • 相关阅读:
    jvm基本结构和解析
    多态的意思
    java中对象的简单解读
    double类型和int类型的区别
    python 解析xml文件
    win10不能映射Ubuntu共享文件
    Qt程序打包
    Ubuntu boot分区文件误删,系统无法启动,怎么解
    ubuntu Boot空间不够问题“The volume boot has only 5.1MB disk space remaining”
    Ubuntu 分辨率更改 xrandr Failed to get size of gamma for output default
  • 原文地址:https://www.cnblogs.com/cuiyansong/p/3866792.html
Copyright © 2011-2022 走看看