.Net迭代器新思路
在通常的情况下,我们非常习惯于用foreach来迭代容器中的元素。虽然比起for循环来说,foreach可以让我们少打一些字母。
如果我们有下面的需求:把一个整数的容器中所有元素乘2,然后保存在新的容器中,那么我们通常会写下下面的代码:
int[] datas = { 1, 3, 2, 5, 7, 94, 1, 8, 42, 74, 8 };2
ArrayList src = new ArrayList(datas);3
ArrayList dest = new ArrayList();4
foreach (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来写一段代码完成任务。
int[] datas = { 1, 3, 2, 5, 7, 94, 1, 8, 42, 74, 8 };2
List<int> src = new List<int>(datas);3
List<int> dest = src.ConvertAll(delegate(int val)4
{5
return val * 2;6
});现在,你看不到foreach循环,而且也不用负责向dest中加入元素,你唯一需要关注的就是“val * 2”。现在都是.Net 3.5了,我们应该换上时髦的Lambda表达式!
int[] datas = { 1, 3, 2, 5, 7, 94, 1, 8, 42, 74, 8 };2
List<int> src = new List<int>(datas);3
List<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。
class MyList<T> : ArrayList2
{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
public delegate void ForEachAction<T>(T val);2
public delegate R CollectAllFunc<T, R>(T val);3
public delegate bool FindAllFunc<T>(T val);
下面我们就来创建ForEach方法、FindAll方法和CollectAll方法。首先,我们来看看ForEach。这个最简单。
public 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方法也使用同样的设计思路:
public 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

11
public 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的元素。
int[] datas = { 1, 3, 2, 5, 7, 94, 1, 8, 42, 74, 8 };2
MyList<int> list = new MyList<int>(datas);3
list.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中可以这么说吧)。给代理赋值的时候,我们可以使用匿名函数的方式:
ForEachAction<int> act = delegate(int val)2
{3
//do something4
};
使用匿名函数在输入上会有些许麻烦,如果仅仅是一小段简单的代码,使用匿名函数可能会显得有点不值得。所以我们可以用Lambda表达式来达到同样的目的,但是可以少输入点字母:
ForEachAction<int> act = val => {/*do something*/};唠叨了这么多,仅仅是对.Net迭代器的一些看法。也算是对在.Net中看到了Ruby式迭代器的一种兴奋。


浙公网安备 33010602011771号