.Net迭代器新思路

在通常的情况下,我们非常习惯于用foreach来迭代容器中的元素。虽然比起for循环来说,foreach可以让我们少打一些字母。

如果我们有下面的需求:把一个整数的容器中所有元素乘2,然后保存在新的容器中,那么我们通常会写下下面的代码:

1int[] datas = 13257941842748 };
2ArrayList src = new ArrayList(datas);
3ArrayList dest = new ArrayList();
4foreach (object obj in src)
5{
6    dest.Add(((int)obj) * 2);
7}

8

 

  对于表达来说,我们真正需要注意的是“dest.Add(((int)obj) * 2);”这行代码。而在这行代码中,最重要的是“((int)obj) * 2”代码。而剩下的代码仅仅是为了辅助完成上面的任务。

真正在开发中,我们可能会经常遇到上面的命题。如果每次都来写foreach,然后是几行的辅助代码,确实感觉有点繁琐。那么我们有什么好的方法吗?能把foreach封装起来就好了。

在.Net的System.Collections.Generic命名空间里,我们会发现List类有下面几个方法:

ConvertAll 将当前 List 中的元素转换为另一种类型,并返回包含转换后的元素的列表。

Find 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List 中的第一个匹配元素。

FindAll 检索与指定谓词所定义的条件相匹配的所有元素。

FindLast 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List 中的最后一个匹配元素。

FindLastIndex 已重载。 搜索与指定谓词所定义的条件相匹配的元素,返回 List 或它的一部分中最后一个匹配项的从零开始的索引。

ForEach List 的每个元素执行指定操作。

这些方法都有一个特殊的参数:代理。这些代理都是在System命名空间中声明的。

下面我们还是沿用刚才的命题,用ConvertAll来写一段代码完成任务。

1int[] datas = 13257941842748 };
2List<int> src = new List<int>(datas);
3List<int> dest = src.ConvertAll(delegate(int val)
4{
5    return val * 2;
6}
);
 

现在,你看不到foreach循环,而且也不用负责向dest中加入元素,你唯一需要关注的就是“val * 2”。现在都是.Net 3.5了,我们应该换上时髦的Lambda表达式!


1int[] datas = 13257941842748 };
2List<int> src = new List<int>(datas);
3List<int> dest = src.ConvertAll(val => val * 2);

 

现在让我们来数数,原本声明dest,foreach外加val * 2的3行代码,现在一行就完成了。而且从代码上你一眼就可以看明白“List<int> dest = src.ConvertAll(val => val * 2);”这句话是什么意思:声明一个dest容器,它应该是指向一个src以“val => val * 2”为规则转化而成的容器。

那么现在你肯定想知道这是怎么做到的。其实并不难,让我们来以ArrayList为基类创建一个自己的List类:MyList。


1class MyList<T> : ArrayList
2{
3    public MyList() { }
4    public MyList(ICollection list) : base(list) { }
5    public MyList(int capacity) : base(capacity) { }
6}

 

好了,在此先创建一个泛型的MyList。继承自ArrayList。接下来,我们可以声明3个代理,负责3种不同的操作:ForEach、FindAll、CollectAll


1public delegate void ForEachAction<T>(T val);
2public delegate R CollectAllFunc<T, R>(T val);
3public delegate bool FindAllFunc<T>(T val);

 

下面我们就来创建ForEach方法、FindAll方法和CollectAll方法。首先,我们来看看ForEach。这个最简单。


1public MyList<T> ForEach(ForEachAction<T> act)
2{
3    foreach (object obj in this)
4    {
5        act((T)obj);
6    }

7    return this;
8}

 

这类方法的设计思路大体是这样的。把变动的代码视为一个代码块,有输入和输出。这样就可以把变动的代码抽象成一个函数。于是我们就可以把变动的代码分析一下,得出需要接受多少参数,然后会返回什么结果。最后设计一个代理来限定这个代码块的大模样。而ForEach方法中,仅仅是固定不变的代码,如foreach循环。在最后之所以返回this,目的是为了链式调用:

list.ForEach().Add()

至于CollectAll和FindAll方法也使用同样的设计思路:


 1public MyList<RType> CollectAll<RType>(CollectAllFunc<T, RType> act)
 2{
 3    MyList<RType> ret = new MyList<RType>();
 4    foreach (object obj in this)
 5    {
 6        ret.Add(act((T)obj));
 7    }

 8    return ret;
 9}

10
11public MyList<T> FindAll(FindAllFunc<T> act)
12{
13    MyList<T> ret = new MyList<T>();
14    foreach (object obj in this)
15    {
16        if(act((T)obj)) ret.Add(obj);
17    }

18    return ret;
19}

 

下面我们来看看这个MyList类的使用,当然,跟List类的使用基本一样。这次的需求增加了一下,需要先打印所有的元素,然后给每个元素×2,然后打印大于10的元素。

1int[] datas = 13257941842748 };
2MyList<int> list = new MyList<int>(datas);
3list.ForEach(val => Console.Write("{0}\t", val))
4    .CollectAll(val => val * 2).FindAll(val => val > 10)
5    .ForEach(val => Console.Write("{0}\t", val));

  这段代码看上去都是一些方法调用,其实它替代了许多foreach循环。不过至于后面两个需求,使用一个foreach循环就可以办到,上面的链式调用对性能有所损失。不过依据个人的爱好吧,使用哪种方式都可以。

至于Lambda表达式,其实是脱胎于代理的一个概念(至少在.Net中可以这么说吧)。给代理赋值的时候,我们可以使用匿名函数的方式:


1ForEachAction<int> act = delegate(int val)
2{
3    //do something
4}
;

 

使用匿名函数在输入上会有些许麻烦,如果仅仅是一小段简单的代码,使用匿名函数可能会显得有点不值得。所以我们可以用Lambda表达式来达到同样的目的,但是可以少输入点字母:

1ForEachAction<int> act = val => {/*do something*/};

   唠叨了这么多,仅仅是对.Net迭代器的一些看法。也算是对在.Net中看到了Ruby式迭代器的一种兴奋。

 

posted on 2008-11-12 14:28  blacktear  阅读(393)  评论(2编辑  收藏  举报