zoukankan      html  css  js  c++  java
  • .Net 闭包理解

    .Net 闭包理解#

    这个东西初看是比较难懂,但是一旦理解之后就很容易了,做笔记以加深印象。且看这题

    example.1
    
    class Program
    {
        static void Main(string[] args)
        {
            var actions = new List<Action>();
    
            for (int i = 0; i < 5; i++)
            {
                actions.Add(() =>
                {
                    Print(i);
                });
            }
    
            actions.ForEach(x => x());
        }
    
        private static void Print(int i)
        {
            Console.WriteLine(i);
        }
    }
    

    运行一下,结果都是5,Why?首先我们都知道,程序执行一个函数的时候,会将这个函数中用到的局部
    变量压入一个栈中,退出这个函数的时候,会将栈中的数据清空。上面的代码,第一个Print函数用的i
    值应该是第一个i(也就是0)所在地址的值,但是自加之后,地址变了,那么第一个Print函数用的i(也就是0)去
    哪里了?我理解的话应该就是变成了一个不安全的地址,访问这个值就会出现问题。为了避免这种情况
    的发生,.Net是如何处理的呢,那就是在所有Print函数执行的时候,不让这个i被清理,于是提升了i的
    作用域(这个现象就叫闭包),我换个写法

    example.2
    
    class Program
    {
        private static int _i;
    
        static void Main(string[] args)
        {
            var actions = new List<Action>();
    
            for (_i = 0; _i < 5; _i++)
            {
                actions.Add(() =>
                {
                    Print(_i);
                });
            }
    
            actions.ForEach(x => x());
        }
    
        private static void Print(int i)
        {
            Console.WriteLine(i);
        }
    }
    

    _注意:_此时是不存在闭包现象的,当然也别以为.Net就是这么做的,并不是。通过IL反汇编工具查看
    example.1的代码,发现它把i封装到了一个匿名的类中,可以这么想,要使一个东西在栈上不被清理,
    那就把它放到堆上...,于是用了个匿名类把i当成其属性,代码如下:

    class Program
    {
        static void Main(string[] args)
        {
            var actions = new List<Action>();
    
            var myClass = new MyClass();
    
            for (var i = 0; i < 5; i++)
            {
                myClass.i = i;
    
                actions.Add(() =>
                {
                    Print(myClass);
                });
            }
    
            myClass.i++;
    
            actions.ForEach(x => x());
        }
    
        private static void Print(MyClass myClass)
        {
            Console.WriteLine(myClass.i);
        }
    }
    
    internal class MyClass
    {
        public int i
        {
            get; set;
        }
    }
    

    如果我们要避免这种情况,该如何做?那就相当于上诉代码每一个循环使用一个新的MyClass

    class Program
    {
        static void Main(string[] args)
        {
            var actions = new List<Action>();
    
            for (var i = 0; i < 5; i++)
            {
                var myClass = new MyClass();
    
                myClass.i = i;
    
                actions.Add(() =>
                {
                    Print(myClass);
                });
            }
    
            actions.ForEach(x => x());
        }
    
        private static void Print(MyClass myClass)
        {
            Console.WriteLine(myClass.i);
        }
    }
    
    internal class MyClass
    {
        public int i
        {
            get; set;
        }
    }
    

    其实申明MyClass这一步也可以省了,就是如下

    class Program
    {
        static void Main(string[] args)
        {
            var actions = new List<Action>();
    
            for (var i = 0; i < 5; i++)
            {
                var i1 = i;
                actions.Add(() =>
                {
                    Print(i1);
                });
            }
    
            actions.ForEach(x => x());
        }
    
        private static void Print(int i)
        {
            Console.WriteLine(i);
        }
    }
    

    其实上面这个写法是Resharper提示闭包之后给出的解决方案。
    以上,就是我看了别人的一些理解,再加上自己的一些理解推敲出来的东西,有许多地方是模糊的,表面的,但
    闭包这东西,不理解好像也没什么影响,这里起个抛砖引玉的作用,欢迎斧正拍砖。

  • 相关阅读:
    问题——虚拟机连接,查本地DNS,查软件位置,payload生成,检测注册表变化
    nmap命令解释
    SMB扫描,SMTP扫描
    操作系统识别,SNMP扫描
    服务扫描——查询banner信息,服务识别
    nmap之扫描端口(附加hping3隐藏扫描)
    scapy简单用法——四层发现
    转载 界面组装器模式
    设计模式=外观模式
    如何进行自动化测试和手工测试
  • 原文地址:https://www.cnblogs.com/HelloMyWorld/p/5330956.html
Copyright © 2011-2022 走看看