利用Reflector把"闭包"看清楚

今天老赵在园子里发了一篇文章"警惕匿名方法造成的变量共享",立即引起了大家的广泛关注(老赵就是园子的"人气天王",呵呵),而且这个问题园子里也有其它几篇文章做了研究
比如"闭包","《你不常用的c#之三》:Action 之怪状 "

如果只是停留在c#2.0/3.0的"简捷且优雅"的代码风格上,初学者确实难理解这个"怪"现象,前二天买了本anytao的“你必须知道的.net”,里面提供了一种研究这类表面"怪"现象的基本方法--IL分析,并推荐了大名鼎鼎的反编译工具"Reflector",下面利用这个工具对其分析一二(高手就不必看了,权当给初学者一些参考)

原始代码一(摘自"《你不常用的c#之三》:Action 之怪状"一文):

代码1
using System;
using System.Collections.Generic;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Action> ls = new List<Action>();
            for (int i = 0; i < 10; i++)
            {                
                ls.Add(() => Console.WriteLine(i));                
            }

            foreach (Action action in ls)
            {
                action();
            }
            System.Console.Read();
        }       
    }  

}

 

结果:一连输出了10行完全相同的"10"(可能并没有按代码编写者的"意图",输出0到9),why?

打开Relector,先做一些设置:打开"View"菜单-->选择"Options",先去掉Show PDB symbols前的勾,然后把Optimization后的下拉框改为".Net 1.0"(众多的"语法糖",比如匿名方法,扩展方法等都是在1.0版本以后出现的,这样设置的目的是去掉这些华丽的外衣,直接反应出原始的c#代码),刚才的代码经过反编译后,大概如下:

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    // Fields
    public int i;

    // Methods
    public void <Main>b__0()
    {
        Console.WriteLine(this.i);
    }
}

 
private static void Main(string[] args)
{
    List<Action> list = new List<Action>();
    Action item = null;
    <>c__DisplayClass2 class2 = new <>c__DisplayClass2();
    class2.i = 0;
    while (class2.i < 10)
    {
        if (item == null)
        {
            item = new Action(class2.<Main>b__0);
        }
        list.Add(item);
        class2.i++;
    }
    foreach (Action action2 in list)
    {
        action2();
    }
    Console.Read();
}

 可以看出,
1.编译器自动生成了一个密封类:<>c__DisplayClass2,里面有一个公有字段i,以及一个公共方法<Main>b__0()--用来输出i
2.再看Main方法中的高亮部分,自始至终,<>c__DisplayClass2就只生成了一个实例class2,至于下面的while里变来变去,也只不过在改变i这个变量(也就是实例class2的成员i),而我们知道“类(class)”是引用类型,实际上class2不过是个引用而已,所以每次用new Action(class2.<Main>b__0)生成item,再list.Add(item)进去后,每个item调用的都是同一个引用,因此最终一连输出10行相同的结果--即数字10,也就是理所当然了

把代码1,稍作修改,如下: 

using System;
using System.Collections.Generic;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Action> ls = new List<Action>();
            for (int i = 0; i < 10; i++)
            {
                int lp = i;
                ls.Add(() => Console.WriteLine(lp));                
            }

            foreach (Action action in ls)
            {
                action();
            }
            System.Console.Read();
        }       
    }  

}

即在循环内部用一个临时变量lp做了一个中转,这次运行的结果,屏幕上输出了0-9共10行不相同的结果

why?

还是用Reflector来看看到底最终的代码是啥?

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    // Fields
    public int tp;

    // Methods
    public void <Main>b__0()
    {
        Console.WriteLine(this.tp);
    }
}

private static void Main(string[] args)
{
    List<Action> list = new List<Action>();
    for (int i = 0; i < 10; i++)
    {
        <>c__DisplayClass1 class2 = new <>c__DisplayClass1();
        class2.tp = i;
        list.Add(new Action(class2.<Main>b__0));
    }
    foreach (Action action in list)
    {
        action();
    }
    Console.Read();

同样,编译器还是自动为我们生成了一个密封类,这一点跟代码1反编译后的一样,关注一下高亮部分,这回<>c__DisplayClass1 class2 = new <>c__DisplayClass1();是放在循环里写的,也就是说10次外循环走下来,一共创建了10个不同的c__DisplayClass1()实例,剩下的就不用多说了,看明白了吧

关于对于这个现象,个人觉得老赵的建议很好:委托创建完后,即时使用--no problem!(其实代码1也可以改成这样)

代码1修改后
using System;
using System.Collections.Generic;
namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Action> ls = new List<Action>();
            for (int i = 0; i < 10; i++)
            {                
                ls.Add(() => Console.WriteLine(i));
                ls[i]();
            }
           
            System.Console.Read();
        }       
    }  
}

结果正常,输出0到9,再一次验证了"立即使用"是没问题的,但如果不是立即使用,就得多想想了

最后,其实本文所说的现象老赵在文中已经讲得很明白了,我在这里只不过向初学者推荐了一下反编译的基本分析方法(当然你如果懂IL的话,可以分析得更透),很多情况下,光看程序表面的现象,是很难想明白的,利用一些工具,找到表象下的本质相对更容易把握。

时间: 2024-09-19 23:52:58

利用Reflector把"闭包"看清楚的相关文章

Netflix严查利用VPN跨境看视频的行为

1月15日消息,据国外媒体报道,Netflix上的影视内容通常都有很严苛的版权播放限制,譬如在美国市场允许播放的内容,在英国就未必享有相应的授权.一些用户为了"解决"这个问题,便采取了利用VPN来"伪装"自己IP的技术手段.此举显然让版权商感到不满,Netflix在周四也开始表态会在未来数周里启动屏蔽此类行为的计划. "那些使用代理或者'解锁'工具的用户,未来将只能够观看其所在地区享有的版权内容."Netflix表示,"我们采取了与其他

新技能,利用Reflector来修改dll引用

继上次<ArcObject10.1降级至10.0>又遇到版本降级问题.通常的方式有: 方案一:重新编译 将源代码加载到解决方案中,修改相应dll的版本,比较快捷的方式是多选后,设置属性中特定版本项为False,VS会自动搜索本机相应版本并映射 方案二:反编译 现实和理想总会存在差距.经常,技术经理给你的是一个个dll,没有源码,或者找不到了.如果我们还要修改dll中的很多内容,这时,就不得不使用反编译技能了.对于.NET程序的反编译,可参考本博客的部分文章. http://www.cnblog

.NET 异常处理的动作策略(Action Policy)

SQL Server 2008基于策略的管理,基于策略的管理(Policy Based Management),使DBA们可以制定管理策略,并将这些策略应用到服务器.数据库以及数据环境中的其他对象上去.基于动作策略(Action Policy)的异常处理使开发人员可以为异常处理制定策略,简单的说,动作策略只是一些可重复使用的一个装饰器,可以很容易应用与方法调用. 异常处理只是一个合乎逻辑的动作策略的一部分,动作策略决定如何对异常做出处理,微软的Enterprise Library的异常处理模块试

javascript使用闭包模拟对象的私有属性和方法_javascript技巧

最近因为做了一个项目,其中涉及到了js私有方法,这个概念在其语言里面是很常见的,很多语言都有private这个关键字,只要在一个类的前面加上private就表示申明了一个私有方法,但是javascript在面向对象的方面没有那么多的特征,他没有专门的private关键字,.要做到这一点就必须使用js自己的一些特性来变相的完成. 首先javascript里面有一个高级特性叫闭包,简单的说js的闭包可以理解成是一种现象或者特性,一般出现在两个函数嵌套的情况下,看例子: function a(){ v

Swift 中闭包的简单使用_Swift

本文主要是介绍Swift中闭包的简单使用,将从"闭包的定义"."闭包的创建.赋值.调用"."闭包常见的几种使用场景","使用闭包可能引起的循环强引用" 四个方面入手,重点介绍闭包如何使用,没有高深的概念,只是专注于实际使用,属于入门级水平,后面还会有关于闭包更加详细和深入理解的文章.希望大家在阅读完本文后能够对闭包有一个整体的理解以及能够简单的使用它. 闭包的定义 在Swift开发文档中是这样介绍闭包的:闭包是可以在你的代码中

【技巧】只利用 Visual Stdio 自带的工具这么找父类?

很多人说只能 F12 看见子类 其实vs里面有一个叫"对象浏览器" 通过这个就可以直接定位父类,不需要利用reflector之类的工具来找父类 具体如下:  

IOS swift3.0 下闭包语法整理_IOS

IOS swift3.0 下闭包语法整理 一.闭包的概念 有oc基础的都知道,闭包其实是oc里面的block,语法格式不一样,但作用是一样的.主要是用于callBack(异步回调)或者两个类之间的通信.它的本质一个函数,一个可执行的代码块,只是这个函数是没有名字的,也就是匿名函数.你也可以把他看作如 int.float一样,是一种数据类型,一种可以作为参数传递的数据类型. 二.基本语法 1.闭包的声明 //定义一个求和闭包 //闭包类型:(Int,Int)->(Int) let add:(Int

Lua中的闭包小结_Lua

前言 在很多语言中都有闭包的概念,而在这里,我将主要对Lua语言的闭包概念进行分析与总结.希望对大家学习Lua有帮助. 什么是闭包? 闭包在Lua中是一个非常重要的概念,闭包是由函数和与其相关的引用环境组合而成的实体.我们再来看一段代码: 复制代码 代码如下: function newCounter()      local i = 0      return function () -- 匿名函数           i = i + 1           return i      end

利用Photoshop制作柔美的人物宣传海报教程

先来看看最终效果图片如下   1.先来新建一个文档大小1200X800这可自定,分辨率:320 ,然后将你准备的素材导入场景中来.     2.我们利用ps矩形选框工具在背景上选择一个区域,来填满画布在这操作你也可以利用自由变换工具,为了看上去更自然我们利用使用"图章"工具,使整个背景浑然一体. 3.接着再利用ps "套索工具"所人物轮廓套取一个大概的区域,如图所示. 4.再我们 ctrl+i再添加滤镜>模糊>高斯模糊,设置半径5-3像素 如下图所示.