| 加入收藏| 设为首页| 联系我们

首页 站长学习 站长之家 源码下载 建站素材 书籍教程 常用工具
 您现在的位置: 动力中国 >> 网络编程 >> ASP.NET教程 >> 文章正文  
 [图文].Net编程接口中的迭代器
 

.Net编程接口中的迭代器

http://www.domcn.org  文章来源:本站收藏  点击数:

  关键字:.Net编程接口中的迭代器

        给大家讲讲用于枚举元素集合的两个接口IEnumerator和IEnumerable。IEnumerator用于实现一个迭代器(相当于以前C++的文章中所说的iterator),它具备列举一个数据结构中所有元素所需要的一些方法和属性。而IEnumerable接口则用于返回一个迭代器对象,一个实现了IEnumerable的类型表示这个类型对象是可以枚举的。
    下面我们先来看看这两个接口的定义吧!

IEnumerator

     迭代器用于枚举一个数据结构中的所有元素。

namespace System.Collections 
{
[ComVisible(true)]
[Guid(496B0ABE-CDEE-11d3-88E8-00902754C43A)]
public interface IEnumerable
{
[DispId(-4)]
IEnumerator GetEnumerator();
}
}
   从上面的定义我们可以看到,一个Emurator具备了枚举一个数据结构中所有元素的最基本能力:
   获取当前元素的的能力:Current属性;
   移动元素指针的能力:MoveNext方法; 
   重置迭代器的能力:Reset方法。
   这里的Current属性是Object类型,也就是可以返回所有类型元素,而与之对应的泛型接口是:System.Collections.Generic.IEnumerator<T>,它除了继承了Ienumerator之外,还增加了一个特定类型的Current属性。
IEnumerable 
   IEnumerable声明一个类型为可枚举的类型,而它的定义很简单,就是返回一个迭代器。
namespace System.Collections 
{
[ComVisible(true)]
[Guid(496B0ABE-CDEE-11d3-88E8-00902754C43A)]
    我们应该对C#风格的遍历语法应该很熟悉了,也就是foreach语句。说到foreach这个东西,其实在C++中也存在,但是是以函数的形式做在库里面的,而对C#来说,它已经被做到语言中去了。在绝大多数情形下,我们应该尽量使用foreach语句来遍历一个集合对象,而不是自己写一个for循环或者其他的while循环等,理由很简单:效率。而foreach语法需要被枚举的对象类型实现了IEnumerable接口。
    与IEnumerable对应的泛型接口是:System.Collections.Generic.IEnumerable<T>。
设计一个集合类

    通常,IEnumerator和IEnumerable是一起使用的。假设我们设计一个属于自己的一个数据结构类MyCollection,并且让他可以被枚举,那么整体上应该怎么设计呢?我们看看下面的代码。
class MyCollection:IEnumerable 
{
public struct MyEmurator : IEnumerator
{
//此处省略实现代码
}
//此处省略部分实现代码
public IEnumerator GetEnumerator()
{
return new MyEmurator(this);
}
}
   这是一个典型的对IEnumerator和IEnumerable的应用方式。几乎所有的System.Collection里面的容器都是都是这样来设计的。将容器类型本身实现IEnumerable,表明容器是可枚举的。而迭代器类型则是一个嵌套类型,通过容器类的接口函数GetEnumerator来返回迭代器的实例。通常一个容器和它的迭代器是紧密相关的,并且一个容器配备一个迭代器已经足以,那么将迭代器定义为嵌套类型,避免了管理的混乱。

实现一个2D List类型

    我们这里说的二维List类型,其实就是实现一个以List为元素的List,简而言之,这个List2D就是用来存放List的一个List;但是我们枚举的时候,并不想要枚举List2D里面的list, 而是想直接枚举list里面的元素。
    我们这里定义了一个泛型类List2D<T> ,实现了IEnumerable<T>接口。这里为了精简代码,这里没有让List2D实现IList接口,只提供了Add、Clear等几个简单方法。不多说了,还是来看看下面的代码吧!
class List2D<T> : IEnumerable<T> 
{
//内嵌迭代器类型
public struct Emurator : IEnumerator<T>
{
//此处代码先不写出
}
private List<List<T>> _lists=new List<List<T>>();//存储列表

public List<T> this[int index]
{
get { return _lists[index]; }
}

public int Count
{
get
{
int count = 0;
foreach (List<T> list in _lists)
{
count += list.Count;
}
return count;
}
}

public void Add(List<T> item)
{
_lists.Add(item);
}

public void Clear()
{
_lists.Clear();
}

#region IEnumerable Members

public IEnumerator GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
#endregion

#region IEnumerable<T> Members

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return new Emurator(this);
}
#endregion
}
   我们再来看看迭代器类型struct Emurator的定义,它是嵌套在List2D之中的,它实现了IEnumerator<T>接口。迭代器的实现详见注释。
class List2D<T> : IEnumerable<T> 
{
public struct Emurator : IEnumerator<T>
{
List2D<T> _list2D; //被枚举的2D list
IEnumerator<List<T>> _listsEmuretor; //列表迭代器
IEnumerator<T> _listEmuretor; //元素迭代器
bool _started; //是否开始枚举
T _current; //当前元素

public Emurator(List2D<T> list2D)
{
_list2D=list2D;
_listsEmuretor = list2D._lists.GetEnumerator();
_listEmuretor=default(IEnumerator<T>);
_started = false;
_current = default(T);
}

#region IEnumerator Members

public object Current
{
get { return _current; }
}

public bool MoveNext()
{
if (!_started) //第一次MoveNext, 需要取第一个列表
{
_started = true;
if (!_listsEmuretor.MoveNext())
return false;

_listEmuretor = _listsEmuretor.Current.GetEnumerator(); //获取第一个list的迭代器
}

while(true)
{
if (!_listEmuretor.MoveNext())
{
//当前列表枚举结束,需要移动到一个列表
if (!_listsEmuretor.MoveNext())
return false; //所有列表遍历完毕,返回false

_listEmuretor = _listsEmuretor.Current.GetEnumerator();
}
else //当前列表还有元素,成功
{
_current = _listEmuretor.Current;
return true;
}
}
}

public void Reset()
{
_listsEmuretor.Reset();
_current = default(T);
_started = false;
}
#endregion

#region IEnumerator<T> Members
T IEnumerator<T>.Current
{
get { return _current; }
}
#endregion

public void Dispose()
{
}
}
}
    真不容易,写了好些代码才把这个2D List的迭代器实现。我们在Main函数里面写一些测试代码,看看它能否正常运行。

static void Main(string[] args) 
{
List2D<string> list2D = new List2D<string>();//2维string列表

List<string> list1 = new List<string>();
list1.Add(list1-1);
list1.Add(list1-2);
list1.Add(list1-3);
list2D.Add(list1); //第一个列表有3个元素

List<string> list2 = new List<string>();
list2D.Add(list2);//第二个列表没有元素

List<string> list3 = new List<string>();
list1.Add(list3-1);
list1.Add(list3-2);
list2D.Add(list3); //第三个列表有2个元素

foreach (string str in list2D)//枚举所有string
{
Console.WriteLine(str);
}

Console.ReadKey();
   运行结果如下。
   list1-1
   list1-2
   list1-3
   list3-1
   list3-2
   我们可以看到运行结果完全正确。 Yield Return

    从前面我们可能发现,有时候写一个迭代器可能是挺麻烦的一个事情,其实需求却可能是挺简单的,但是我们却要写大量代码来实现一个迭代器。所幸的是,C#给我们提供一个独门利器:Yield Return! 利用yield return,我们不需要写一个迭代器就能够实现GetEnumerator函数了。
Yield return不同于普通函数的Return,它从作用上来说,相当于每调用一次yield return,就返回一个被枚举的元素。Yield Return语法只能用在返回值类型为IEnumerator的函数中。 我们现在来看看,List2D的GetEnumerator函数如何用yield return来实现,从而使我们不用再创建一个自定义的Emurator类型。
IEnumerator<T> IEnumerable<T>.GetEnumerator() 
{
foreach (List<T> list in _lists)
{
foreach (T item in list)
{
yield return item;
}
}
}
    7行!包括4个花括弧仅仅用了7行代码,就实现了一个迭代器的所有功能,而前面我们为了实现一个迭代器,写了数十行的代码,这对编码效率上来说,这是多么大的一个提升啊。当然,并非所有情形下,用yield return都是合适,但是在一般的需求之下,它是最好的选择。
习惯了传统编程语言的人,可能会比较难易理解Yield Return这个东西,而且更会对它是到底是如何实现的充满了疑问。其实Yield Return是C#从语言层面上提供的一种简化代码的语法形式而已,而归根到底,其实是C#的编译器辅助我们完成了一部分编码工作—它为我们自动创建了一个匿名的迭代器类型,并且用那个迭代器实现了我们在使用yield return语义所实现的迭代功能。
    如果你对上面的论断产生怀疑的话,我们可以借助VS.net自带的IL查看工具ildasm.exe来查看我们用yield return代码编译后的exe文件。Ildasm.exe (VS2005)通常存放在“C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\”目录下。
    从下面Ildasm.exe的截图中我们可以看见List2D类型中有个嵌套类型GetEnumerator>d__0<T>,而这个恰恰就是C#编译器为我们生成的匿名迭代器类型。

   在感叹C#编译器的智能的时候,我们有必要对yield return的实现做一个更加深入的了解,那就是查看它自动生成的迭代器的代码究竟是怎么样的,与我们自己写的迭代器相比,究竟有多大差别。使用ildasm可以查看IL代码,不过这是非常痛苦的一件事情,即使你对IL的所有指令都背得滚瓜烂熟,同样也是一间非常痛苦的事情。所以,这里我想向大家推荐一个代码学习利器,.Net的反编译器“.Net Reflector”,它可以将编译好的.Net程序反编译为C#代码,并且有着比较高的还原率,而且这个工具本身还在不断升级当中。你可以从http://www.aisto.com/roeder/dotnet免费下载这个软件
    Yield Return可以在GetEnumerator函数中任意地方使用,而我们的程序结构一般会包括顺序结构、循环和分支选择等。为了弄清编译器对yield return的处理细节,我们先设计一个简单的类,它仅仅使用yield return先返回一个-1,再返回一个100,然后就结束了,这是一个再简单不过的顺序执行结构了,下面是这个类的代码:
class TestYieldReturn:IEnumerable 
{
public IEnumerator GetEnumerator()
{
yield return -1;
yield return 100;
}
}
    我们将这段程序编译好,然后用.NET Reflactor将其进行反编译,于是我们得到了编译器自动生成的迭代器的代码:

[1] [2] 下一页


.Net编程接口中的迭代器
  • 上一篇文章:

  • 下一篇文章:
  •  热门文章
    普通文章 电子邮件改头换面 四公司畅谈未
    普通文章 PC病毒史上最声名狼藉的八大病
    普通文章 Rails系统中的AJAX开发技术简析
    普通文章 基于ASP.NET AJAX框架实现表单
    普通文章 开发ASP.NET AJAX客户端定制行
    普通文章 用JFreeChart对JSP报表进行增强
    普通文章 SQL Server 2005上的CLR和ADO.
    普通文章 SQL Server 2005的XML支持机制
    普通文章 Firefox中标签式浏览技巧大全
    普通文章 Tomcat中的Session和Cookie大揭
     
     推荐文章
    推荐文章 把Google地图嵌入网页 就是这么
    推荐文章 迅雷搜索候选资源出错的解决
    推荐文章 轻松去除迅雷里的各种广告和资
    推荐文章 突破限制 免费领养到QQ空间五级
    推荐文章 Rational统一过程RUP贴近中小软
    推荐文章 构建自己的轻量级XML DOM分析程
    推荐文章 WPS Office 2007技巧:妙用配置
    推荐文章 Excel 2007:求余数函数实用进阶
    推荐文章 浅谈ASP.NET的Postback
    推荐文章 软件开发中项目需求管理简述
     
     相关文章
    没有相关文章
    设为首页 | 加入收藏 | 广告合作 | 联系站长 | 版权申明 |
    动力中国为网友提供免费学习资料,可用资源,如果您认为我们的相关内容侵害到了您的权利请联系管理员
    Copyright © 2006-2008 domcn.org All Rights Reserved.