Lambda高手之路第三部分

转http://www.cnblogs.com/lazycoding/archive/2013/01/06/2847587.html

背后的秘密-MSIL

通过著名的LINQPad,我们可以更深入的查看MSIL代码而没有任何秘密。下图是一个LINQPad的使用截图

我们会看三个例子,第一个Lambda表达式如下:

Action<string> DoSomethingLambda = (s) =>
{
    Console.WriteLine(s);// + local
};

对应的普通函数是这样的

Action<string> DoSomethingLambda = (s) =>
{
    Console.WriteLine(s);// + local
};

生成的MSIL代码片段如下:

DoSomethingNormal:
IL_0000:  nop
IL_0001:  ldarg.1
IL_0002:  call        System.Console.WriteLine
IL_0007:  nop
IL_0008:  ret
<Main>b__0:
IL_0000:  nop
IL_0001:  ldarg.0
IL_0002:  call        System.Console.WriteLine
IL_0007:  nop
IL_0008:  ret       

最大的不同是方法的名称用法不同。而不是声明。事实上。声明是完全一样的。编译器在类里面创建了一个新的方法来实现这个方法。这没什么新东西,仅仅是为了我们使用Lambda表达式方便代码编写。从MSIL代码中,我们做了同样的事情。在当前对象里调用了一个方法。

我们在下图里演示一下编译器所做的修改。在这个图例。我们可以看到编译器把Lambda表达式移动成了一个固定的方法。


第二个例子将展示Lambda表达式真正的奇妙之处,在这个例子里。我们既使用了有着全局变量的普通方法也使用了有捕获变量的Lambda表达式。代码如下

void Main()
{
    int local = 5;

    Action<string> DoSomethingLambda = (s) => {
        Console.WriteLine(s + local);
    };

    global = local;

    DoSomethingLambda("Test 1");
    DoSomethingNormal("Test 2");
}

int global;

void DoSomethingNormal(string s)
{
    Console.WriteLine(s + global);
}

没什么不同的似乎。关键是:lambda表达式如何被编译器处理

IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
IL_0005:  stloc.1
IL_0006:  nop
IL_0007:  ldloc.1
IL_0008:  ldc.i4.5
IL_0009:  stfld       UserQuery+<>c__DisplayClass1.local
IL_000E:  ldloc.1
IL_000F:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
IL_0015:  newobj      System.Action<System.String>..ctor
IL_001A:  stloc.0
IL_001B:  ldarg.0
IL_001C:  ldloc.1
IL_001D:  ldfld       UserQuery+<>c__DisplayClass1.local
IL_0022:  stfld       UserQuery.global
IL_0027:  ldloc.0
IL_0028:  ldstr       "Test 1"
IL_002D:  callvirt    System.Action<System.String>.Invoke
IL_0032:  nop
IL_0033:  ldarg.0
IL_0034:  ldstr       "Test 2"
IL_0039:  call        UserQuery.DoSomethingNormal
IL_003E:  nop         

DoSomethingNormal:
IL_0000:  nop
IL_0001:  ldarg.1
IL_0002:  ldarg.0
IL_0003:  ldfld       UserQuery.global
IL_0008:  box         System.Int32
IL_000D:  call        System.String.Concat
IL_0012:  call        System.Console.WriteLine
IL_0017:  nop
IL_0018:  ret         

<>c__DisplayClass1.<Main>b__0:
IL_0000:  nop
IL_0001:  ldarg.1
IL_0002:  ldarg.0
IL_0003:  ldfld       UserQuery+<>c__DisplayClass1.local
IL_0008:  box         System.Int32
IL_000D:  call        System.String.Concat
IL_0012:  call        System.Console.WriteLine
IL_0017:  nop
IL_0018:  ret         

<>c__DisplayClass1..ctor:
IL_0000:  ldarg.0
IL_0001:  call        System.Object..ctor
IL_0006:  ret      

和第一个例子一样。机制相同。编译器把lambda表达式移动到一个方法里。但是不同的是,编译器这次还生成了一个类。编译器为我们的lambda 表达式生成的方法会放在类里,这就给了捕获的变量一个全局的作用域,通过这样。Lambda表达式可以访问局部变量。因为在MSIL里。它是类实例里面的 一个全局变量。

所有的变量因此就可以在新生成的类的对象里赋值/读取了。这解决了变量之间的引用问题。(其实就是只保留了对该类实例的引用。)编译器也足够智能之 会把这些捕获的变量放到类里面。因此,当我们使用Lambda的时候才没有太大的性能问题。无论如何。注意。由于保持了对lambda表达式的引用,因此 可能造成内存泄漏。只要方法还在。变量就仍然存活。显而易见。而现在我们知道了原因。

我们再次用图示来说明。这这种闭包情况下里。不仅仅方法会被移动。捕获的变量也会被移动。所有被移动了的对象会被放到一个新生成的类里。因此一个没有名称的类就隐式的出现了。

时间: 2024-10-25 10:22:03

Lambda高手之路第三部分的相关文章

Lambda高手之路第一部分

转http://www.cnblogs.com/lazycoding/archive/2013/01/06/2847574.html 介绍 Lambda表达式是使代码更加动态,易于扩展并且更加快速(看完本文你就知道原因了)的强有力的工具.也可以用来降低潜在的错误.同时可以利用静态输入和智能提示,就像VS里一样. Lambda表达式在.net framework 3.5中提出来.并且在LINQ和ASP.NET MVC内部的一些技术中扮演了相当重要的角色.如果你考虑一下ASP.NET MVC中各类控

Lambda高手之路第二部分

转http://www.cnblogs.com/lazycoding/archive/2013/01/06/2847579.html 闭包的影响 为了展示闭包的影响,我们看下面这个例子. var buttons = new Button[10]; for(var i = 0; i < buttons.Length; i++) { var button = new Button(); button.Text = (i + 1) + ". Button - Click for Index!&q

Python高手之路【三】python基础之函数

基本数据类型补充: set 是一个无序且不重复的元素集合 1 class set(object): 2 """ 3 set() -> new empty set object 4 set(iterable) -> new set object 5 6 Build an unordered collection of unique elements. 7 """ 8 def add(self, *args, **kwargs): # r

[js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具)

之前,我写了一个arc函数的用法:[js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形). arcTo: cxt.arcTo( cx, cy, x2, y2, 半径 ) cx,cy表示控制点的坐标,x2,y2表示结束点的坐标,如果我们想画一条弧线,需要提供3个 http://pic.cnhubei.com/space.php?uid=4593&do=album&id=1092946http://pic.cnhubei.com/space.php?ui

巨人大哥谈Java工程师高手之路

巨人大哥谈Java工程师高手之路 JVM方面 JVM内存结构 堆.栈.方法区.直接内存.堆和栈区别 Java内存模型 内存可见性.重排序.顺序一致性.volatile.锁.final 垃圾回收 内存分配策略.垃圾收集器(G1).GC算法.GC参数.对象存活的判定 JVM参数及调优 Java对象模型 oop-klass.对象头 HotSpot 即时编译器.编译优化 类加载机制 classLoader.类加载过程.双亲委派(破坏双亲委派).模块化(jboss modules.osgi.jigsaw)

[js高手之路]原型对象(prototype)与原型链相关属性与方法详解

一,instanceof: instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型. 我在之前的两篇文章 [js高手之路]构造函数的基本特性与优缺点 [js高手之路]一步步图解javascript的原型(prototype)对象,原型链 已经分享过了. function CreateObj(uName) {             this.userName = uName;             this.showUserName = function

Android企业级最佳实践高手之路

如何从一个Android程序员到成为一个高手级别的Android开发者和架构师,是每个Android开发者和管理者关心的核心问题,成功的从一个Android程序员到架构师,需要掌握: 1, Android开发与架构,具备Android系统式如何驾驭开发者与架构者的的能力: 2, 通晓Android程序开发的最佳模式,当你直到这个最佳模式的时候,你会发现AsyncTask是Android的败笔,而且这个败笔一直未能够在版本升级中解决: 3, 理解Android程序开发和运行背后的控制者: 4, 合

[js高手之路]深入浅出webpack教程系列9-打包图片(file-loader)用法

[js高手之路]深入浅出webpack教程系列索引目录: [js高手之路]深入浅出webpack教程系列1-安装与基本打包用法和命令参数 [js高手之路]深入浅出webpack教程系列2-配置文件webpack.config.js详解(上) [js高手之路]深入浅出webpack教程系列3-配置文件webpack.config.js详解(下) [js高手之路]深入浅出webpack教程系列4-插件使用之html-webpack-plugin配置(上) [js高手之路]深入浅出webpack教程系

[js高手之路]从原型链开始图解继承到组合继承的产生

于javascript原型链的层层递进查找规则,以及原型对象(prototype)的共享特性,实现继承是非常简单的事情 一.把父类的实例对象赋给子类的原型对象(prototype),可以实现继承 1 function Person(){ 2 this.userName = 'ghostwu'; 3 } 4 Person.prototype.showUserName = function(){ 5 return this.userName; 6 } 7 function Teacher (){}