CLR 中匿名函数的实现原理浅析

函数

CLR 中匿名函数的实现原理浅析

C# 2.0中提供了通过delegate实现匿名函数功能,能有效地减少用户的薄记代码工作,例如

以下为引用:

...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...

可以被简化为直接使用匿名函数构造,如

以下为引用:

...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...

关于匿名函数的使用方法可以参考Jeffrey Richter的Working with Delegates Made Easier with C# 2.0一文。简要说来就是C#编译器自动将匿名函数代码转移到一个自动命名函数中,将原来需要用户手工完成的工作自动完成。例如构造一个私有静态函数,如

以下为引用:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}

被编译器自动转换为

以下为引用:

class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}

private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}

而这里自动生成的函数是否为static,编译器根据使用此函数的地方是否static决定。这也是为什么C# 2.0规范里面禁止使用goto, break和continue语句从一个匿名方法里跳出,或从外面跳入其中的原因,因为他们代码虽然写在一个作用域里面,但实际上实现上并不在一起。
更方便的是编译器可以根据匿名函数使用的情况,自动判断函数参数,无需用户在定义时指定,如

以下为引用:

button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };

在不使用参数时,完全等价于

以下为引用:

button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };

相对于匿名函数的实现来说,比较复杂的是匿名函数对于其父作用域中变量的使用及其实现。MS的Grant Ri在其blog上有一系列的讨论文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers

需要解决的问题有两个:一是不在一个变量作用域中的匿名函数如何访问父函数和类的变量;二是匿名函数使用到的变量的生命周期必须与其绑定,而不能与父函数的调用生命周期绑定。这两个问题使得C#编译器选择较为复杂的独立类封装方式实现匿名函数和相关变量生命周期的管理。

首先,匿名函数使用到的父函数中局部变量,无聊是引用类型还是值类型,都必须从栈变量转换为堆变量,以便在其作用域外的匿名函数实现代码可以访问并控制生命周期。因为栈变量的生命周期与其所有者函数是一致的,所有者函数退出后,其堆栈自动恢复到调用函数前,也就无法完成变量生命周期与函数调用生命周期的解耦。
例如下面这个简单的匿名函数中,使用了父函数的局部变量,虽然此匿名函数只在父函数里面使用,但C#编译器还是使用独立类对其使用到的变量进行了包装。

以下为引用:

delegate void Delegate1();

public void Method1()
{
int i=0;

Delegate1 d1 = delegate() { i++; };

d1();
}

自动生成的包装代码类似如下

以下为引用:

delegate void Delegate1();

private sealed class __LocalsDisplayClass$00000002
{
public int i;

public void __AnonymousMethod$00000001()
{
this.i++;
}
};

public void Method1()
{
__LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
local1.i = 0;

Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);

d1();
}

但对于有多个局部变量作用域的情况就比较复杂了,例如Grant Ri在其例子中给出的代码

以下为引用:

delegate void NoArgs();

void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
int outer = 0;
for (int i = 0; i < 10; i++)
{
int inner = i;
methods[i] = delegate {
Console.WriteLine("outer = {0}", outer++);
Console.WriteLine("i = {0}", i);
Console.WriteLine("inner = {0}", ++inner);
};
methods[i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}

就需要一个类封装变量outer;一个类封装变量i;另外一个类封装inner和匿名函数,并引用前面两个封装类的实例。因为变量outer、i和inner有着不同的作用域,呵呵。伪代码如下:

以下为引用:

private sealed class __LocalsDisplayClass$00000008
{
public int outer;

};
private sealed class __LocalsDisplayClass$0000000a
{
public int i;

};
private sealed class __LocalsDisplayClass$0000000c
{
public int inner;

public __LocalsDisplayClass$00000008 $locals$00000009;
public __LocalsDisplayClass$0000000a $locals$0000000b;

public void __AnonymousMethod$00000007()
{
Console.WriteLine("outer = {0}", this.$locals$00000009.outer++);
Console.WriteLine("i = {0}", this.$locals$0000000b.i);
Console.WriteLine("inner = {0}", ++this.inner);
}
};

public void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];

__LocalsDisplayClass$00000008 local1 = new __LocalsDisplayClass$00000008();
local1.outer = 0;

__LocalsDisplayClass$0000000a local2 = new __LocalsDisplayClass$0000000a();
local2.i = 0;

while(local2.i < 10)
{
__LocalsDisplayClass$0000000c local3 = new __LocalsDisplayClass$0000000c();
local3.$locals$00000009 = local1;
local3.$locals$0000000b = local2;
local3.inner = local1.i;

methods[local2.i] = new NoArgs(local3.__AnonymousMethod$00000007);
methods[local2.i]();
}

for (int j = 0; j < methods.Length; j++)
methods[j]();
}

总结其规律就是每个不同的局部变量作用域会有一个单独的类进行封装,子作用域中如果使用到父作用域的局部变量,则子作用域的封装类引用父作用域的封装类。相同作用域的变量和匿名方法由封装类绑定到一起,维护其一致的生命周期。

相对于MS较为复杂的实现,Delphi.NET对嵌套函数则使用较为简单的参数传递方式,因为嵌套函数没有那么复杂的变量生命期管理要求,如

以下为引用:

procedure SayHello;
var
Name: string;

procedure Say;
begin
WriteLn(Name);
end;
begin
Name := 'Flier Lu';

Say;
end;

系统生成函数Say代码时,将使用到的上级变量如Name放入到一个自动生成的类型($Unnamed1)中,然后作为函数参数传递给Say函数,伪代码类似

以下为引用:

type
$Unnamed1 = record
Name: string;
end;

procedure @1$SayHello$Say(var UnnamedParam: $Unnamed1);
begin
WriteLn(UnnamedParam.Name);
end;

procedure SayHello;
var
Name: string;
Unnamed1: $Unnamed1;
begin
Name := 'Flier Lu';

Unnamed1.Name := Name;

Say(Unnamed1);
end;

时间: 2024-10-05 23:28:28

CLR 中匿名函数的实现原理浅析的相关文章

Javascript自执行匿名函数(function() { })()的原理浅析_javascript技巧

函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数指没有指定函数名或指针的函数,自执行匿名函数只是其中一种,下文中称这种函数为:自执行函数 下面是一个最常见的自执行函数: // 传统匿名函数 (function() { alert('hello'); })(); 这段代码的执行效果就是在页面再载入时弹出:"hello" 是什么促使它自动执行的?,来看下面的代码 // 在传统写法上去掉小括号,并在前面加上运算符 ~,!,+,- ~function(){

CLR中匿名函数实现原理浅析

C#2.0中提供了通过delegate实现匿名函数功能,能有效地减少用户的薄记代码工作,例如 ... button1.Click += new EventHandler(button1_Click); ... void button1_Click(Object sender, EventArgs e) { // Do something, the button was clicked... } ... 可以被简化为直接使用匿名函数构造,如 ... button1.Click += delegat

JavaScript中匿名函数用法实例

 这篇文章主要介绍了JavaScript中匿名函数用法,实例分析了javascript中匿名函数的功能.定义及具体用法,需要的朋友可以参考下     本文实例讲述了JavaScript中匿名函数用法.分享给大家供大家参考.具体分析如下: JS中可以不用给函数名称,而是在使用时直接通过function把函数体赋值给相关的事件或者变量. 下面的JS代码给按钮设定了一个onclick事件,使用了匿名函数 ? 1 2 3 4 5 6 7 8 9 <form action="#">

JS中匿名函数嵌套匿名函数

问题描述 JS中匿名函数嵌套匿名函数 求详细说明为什么其中test=3明白 然后匿名函数中的匿名函数怎么调用 如果还有fn(4) fn(5) 为什么传入的都是b的值 解决方案 执行完fn(2)后,fn对应的function变了,变成 function(b){ console.log(test+b); } 所以后面fn(3),fn(4),fn(n)都是执行function(b)这个函数 解决方案二: 执行完fn(2)后,fn对应的function变了,变成 function(b){ console

js中匿名函数的创建与调用方法分析_javascript技巧

本文实例分析了js中匿名函数的创建与调用方法.分享给大家供大家参考.具体实现方法如下: 匿名函数就是没有名字的函数了,也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数.最经常用作回调函数(callback)参数的值,很多新手朋友对于匿名函数不了解.这里就来分析一下. function 函数名(参数列表){函数体;} 如果是创建匿名函数,那就应该是: function(){函数体;} 因为是匿名函数,所以一般也不会有参数传给他. 为什么要创建匿名函数呢?在什么情况下会使用到匿

Javascript中匿名函数的调用与写法实例详解(多种)_javascript技巧

Javascript中定义函数的方式有多种,函数直接量就是其中一种.如var fun = function(){},这里function如果不赋值给fun那么它就是一个匿名函数.好,看看匿名函数的如何被调用. 方式1,调用函数,得到返回值.强制运算符使函数调用执行 (function(x,y){ alert(x+y); return x+y; }(3,4)); 方式2,调用函数,得到返回值.强制函数直接量执行再返回一个引用,引用再去调用执行 (function(x,y){ alert(x+y);

jQuery中noconflict函数的实现原理分解_jquery

jQuery中,noconflict是用来防止变量冲突,用来释放变量控制权的一个重要方法.我们知道,jQuery中对外提供有两个全局变量,$和jQuery,虽然jQuery只产生了两个全局变量,极少情况下才会出现冲突,但是如果网页中如果包涵较多的类库,有自定义$或jQuery全局变量的存在时,就产生冲突. jQuery提供的noconflict函数很好的解决了变量冲突问题,无论是$或者jQuery冲突都可以解决,接下来我们就来分析一下jQuery的冲突处理. 先来看一下jQuery源码中noco

js中匿名函数的创建与调用方法分析

 匿名函数就是没有名字的函数了,也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数.最经常用作回调函数(callback)参数的值,很多新手朋友对于匿名函数不了解.这里就来分析一下. function 函数名(参数列表){函数体;} 如果是创建匿名函数,那就应该是: function(){函数体;} 因为是匿名函数,所以一般也不会有参数传给他. 为什么要创建匿名函数呢?在什么情况下会使用到匿名函数.匿名函数主要有两种常用的场景,一是回调函数,二是直接执行函数. 回调函数,像a

js中匿名函数的创建与调用方法

  function 函数名(参数列表){函数体;} 如果是创建匿名函数,那就应该是: function(){函数体;} 因为是匿名函数,所以一般也不会有参数传给他. 为什么要创建匿名函数呢?在什么情况下会使用到匿名函数.匿名函数主要有两种常用的场景,一是回调函数,二是直接执行函数. 回调函数,像ajax的异步操作,就需要回调函数.这里就不详解.关于直接执行函数,我看一个例子就明白了: 代码如下   <script language="javascript"> var a