第2讲:匿名方法、迭代器

2005.8.24 李建忠

Agenda

使用匿名方法

匿名方法机制

使用迭代器

迭代器机制

讲座总结

 

匿名方法的由来

没有匿名方法的时候(C# 1.0)

image

有了匿名方法之后(C# 2.0)

image

委托表达的是一段代码,引入匿名方法之后,去掉了桥接的方法,直接让委托和代码关联。它虽然没有new关键字,但是它还是做了委托实例化的事情,编译器会自动为我们加上一个具体方法。

它的语法是用delegate关键字,后面的括号跟着代码块来表示,让方法不再需要名字。这里如果没有用到参数,可以不写方法的参数。这种匿名方法是一种语法的简化,适用于只使用一次的方法。


匿名方法简介

匿名方法允许我们以一种“内联”的方式来编写方法代码,将代码直接与委托实例相关联,从而使得委托实例化的工作更加直观和方便。

匿名方法的几个相关问题:

-参数列表

-返回值

-外部变量

 

匿名方法的参数

匿名方法可以在delegate关键字后跟一个参数列表(可以不指定),后面的代码块则可以访问这些参数;

image

注意“不指定参数列表”与“参数列表为空”的区别

image

 

匿名方法的返回值

如果委托类型的返回值类型为void,匿名方法里便不能返回任何值;

如果委托类型的返回值类型不为void,匿名方法里返回的值必须和委托类型的返回值兼容:

image

这里第一个方法返回值类型为void,不写return也可以。

 

匿名方法的外部变量

一些局部变量和参数有可能被匿名方法所使用,它们被称为“匿名方法的外部变量”。

外部变量的生存期会由于“匿名方法的捕获效益”而延长——一直延长到委托实例不被引用为止。

image

匿名方法不是方法,它只是一个委托实例,如果匿名方法不被调用,那么里面的代码不会执行。如果我们在Invoke之后输出factor参数的值,那么factor的值是不变的。如果我们调用过匿名方法f了n次,那么factor的值就会增加0.2*n个值。这是因为factor被匿名方法给捉住了,它可以在匿名方法内部被访问,这样外部变量的生存期就会被延长。

委托类型的推断

C# 2.0允许我们在进行委托实例化时,省略掉委托类型,而直接采用方法名,C#编译器会做合理的推断。

C# 1.0中的做法

image

C# 2.0中的做法

image

这里要省略委托类型的话,一定要只写方法名,而不能写后面的括号以及参数,如果写了括号就表示再调用一遍方法了。只写方法名表示传入方法的入口点地址,即方法的指针。

这些语法的简化都是编译器默默做的东西,为我们工作提供便利,而对于在运行时是没有什么变化的,并不会对性能有什么优化。

 

匿名方法机制

C# 2.0中的匿名方法仅仅是通过编译器的一层额外处理,来简化委托实例化的工作。它与C# 1.0的代码不存在根本性的差别。

通过ILDasm.exe反汇编工具,我们可以获得对匿名方法的深入了解:

-静态方法中的匿名方法

-实例方法中的匿名方法

-匿名方法中的外部变量

 

静态方法中的匿名方法

image

上面的代码被编译器转换为:

image

因为F是一个静态方法,编译器会推断出Method1也是静态方法。具体编译器产生的名字当然不会是Method1,会是一个很混乱的名字,不希望被我们调用。

实例方法中的匿名方法

image

上面的代码被编译器转换为:

image

因为F方法是一个实例方法,所以Method1也会被编译器创建为实例方法。

 

匿名方法中的外部变量

image

上面的代码被编译器转换为:

image

编译器首先创建了一个实例类Temp,里面放置了实例方法Method1和字段y,F在使用方法的时候,会先new一个Temp类型,然后把y值传入Temp实例,再去调用Method1方法。这种方案已经算是最优雅的解决方案了,y的生命周期被委托给拖住,被迫延长,只要不失去委托的引用,就不会被销毁。以前y我们以为在栈上,现在y已经变到托管堆里面了,成本相对提高,性能下降。而且匿名方法很可能会改变外部变量,还可能会导致代码混乱。因此如果不是不要的情况,不要大规模使用匿名方法的外部变量

 

C# 1.0中的foreach

没有迭代器的时候,创建一个可用于foreach的集合(C# 1.0):

image

对集合使用foreach语句:(col是MyCollection的实例变量)

image

相当于:

image

 

C# 2.0中的迭代器

使用迭代器创建用于foreach的集合(C# 2.0):

image

我们只需要实现GetEnumerator方法,返回IEnumerator接口或泛型接口。方法的实现用yield return实现,这就是迭代器的实现方式。这个关键字其实是每一次返回一个元素的遍历作用,在它内部还是实现了1.0的实现方法。

使用foreach语句:

image

使用迭代器创建倒序遍历:

image

使用yield return产生枚举元素:

image

使用yield break中断迭代:

image

yield break即不同于循环中的break也不同于return,这个概念是不一样的。yield break相当于让MoveNext返回值是false,结束迭代。如果是不使用yield break方法,那么迭代器会迭代完所有元素之后才将MoveNext置为false,结束迭代。

 

迭代器机制

C# 2.0中的迭代器同样是通过编译器的一层额外处理,来简化创建可用于foreach的枚举集合的工作。

通过ILDasm.exe反汇编工具,我们可以获得对迭代器的深入理解:

-迭代器中的GetEnumerator()方法

-迭代器中的嵌套类

-迭代器中的yield语句

 

代码演示-使用ILDasm剖析迭代器机制

image

yield return是表示的不是一般意义的return,而是对每一个元素进行返回。

image

通过IL代码可以看到,编译器已经自动为我们生成了一个内部类,实现了IEnumerator接口(泛型版和非泛型版),它内部会维护一个状态机,不断地遍历集合中的元素。

 

讲座总结

匿名方法允许我们以一种“内联”的方式将代码直接与委托实例相关联,从而使得委托实例化的工作更加直观和方便。迭代器允许我们更加方便地编写应用于foreach语句的枚举集合。

对于类似于C#这样的高级语言,掌握好它一个很重要的地方就是掌握编译器在背后为我们做了哪些工作。C# 2.0中的匿名方法和迭代器都是通过在编译层面进行一些额外的处理,进而来简化程序员的工作。

2010.10.29

你可能感兴趣的:(迭代器)