转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表达式的引用,因此 可能造成内存泄漏。只要方法还在。变量就仍然存活。显而易见。而现在我们知道了原因。

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

最新文章

  1. ARM-汇编指令集(总结)
  2. html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?
  3. linux tar order
  4. c# dynamic动态类型和匿名类
  5. WINDOWS渗透与提权总结(1)
  6. Hibernate中的query.setFirstResult(),query.setMaxResults();
  7. C库函数笔记
  8. Linux学习之wget命令
  9. 《C++语言基础》实践參考——数组作数据成员
  10. 消息同步调用-- ESFramework 4.0 进阶(07)
  11. PAT甲级1078 Hashing【hash】
  12. tftp 传输文件
  13. DPDK 网卡绑定和解绑
  14. Linux od命令(以指定进制显示文件)
  15. Microsoft Dynamics CRM 2013 安装 报表服务出现“ SQL Server Reporting Services 帐户是本地用户且不受支持 ”错误的解决方法
  16. PHP源代码生成 main/config.w32.h
  17. BZOJ2938:[POI2000]病毒(AC自动机)
  18. [转]VS2015编译的程序在其他机器上缺少msvcp120.dll
  19. eclipse 在线安装 properties 插件
  20. 10723 Cyborg Genes (LCS + 记忆化搜索)

热门文章

  1. python中打印文件名,行号,路径
  2. 创建成功的Python项目
  3. [思路]为什么要做一个Web服务器
  4. HDU2571:命运(DP)
  5. bin文件格式分析
  6. C# - 接口的继承
  7. 基于visual Studio2013解决C语言竞赛题之1047百马问题
  8. 纯css实现苹果表盘动画
  9. Endnote从头开始五:修改output style(转载)
  10. Customize Spring @RequestParam Deserialization for Maps and/or Nested Objects