分享
 
 
 

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

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

http://flier_lu.blogone.net/?id=1397624

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;

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有