为了账号安全,请及时绑定邮箱和手机立即绑定

利用Reflector把闭包看清楚

标签:
C#

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

原始代码一:

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也可以改成这样)


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的话,可以分析得更透),很多情况下,光看程序表面的现象,是很难想明白的,利用一些工具,找到表象下的本质相对更容易把握。  

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消