本章内容主要针对 C# 中的IEnumerable及其相关内容做详细的解释。
你真的了解Foreach的本质是什么吗?你对yield关键有多少了解呢?希望这篇文章可以让你更清楚的认识你常常会使用的IEnumerable。
适合人群:对C#一定使用基础
阅读方式:浏览
这是我们除了for 循环之外可能用到最多的循环语句,它写起来比for循环要舒服而且更易于去理解。
但有时候你会不经意的被它简单的外表所迷惑而造成一些错误(比如在foreach中修改列表的值)。接下来我们会深入了解一下foreach的具体实现,当清楚了解之后我们的使用将会更加得心应手。
//C#
int[] array = new int[]{1,2,3,4,5};
foreach(int item in array){...}
//CIL 类似生成如下
int[] tempArray;
int[] array = new int[]{1,2,3,4,5}
tempArray = array;
for(int counter = 0;(counter < tempArray.Length);counter++)
{
int item = tempArray[counter];
...
}
能这样变化是因为数组满足 固定长度 和 索引操作[]
由此也可以看出来,Foreach时不要修改集合
对于不满足上述所说的条件,就需要实现IEnumerable接口
IEnumerable/IEnumerable< T > 是.NET实现集合的一个关键。集合的本质其实就是一个类,并且最起码实现了**IEnumerable/IEnumerable< T >**所规定的方法。
IEnumerable使类成为集合
Interface IEnumerable
{
public System.Collections.IEnumerator GetEnumerator (); //这个里面具体实现了 yield return 机制
}
Interface IEnumerator
{
public object Current{get;}
public bool MoveNext();
public void Reset();
}
IEnumerable的foreach
//C#
IEnumerable array = new IEnumerable(){1,2,3,4,5};
foreach(int item in array){...}
//CIL 类似生成如下
....
IEnumerator ator = array.GetEnumerator()
while(ator.MoveNext())
{
ator = array.GetEnumerator()
int item = array.Current;
...
}
C#编译器不要求一定要实现IEnumerable/IEnumerable< T >才能用foreach对数据类型进行迭代。实际上,编译器采取了一种**“看起来像”**的名称查找方式,即 只要查找到其含有 GetEnumerator()方法,这个方法返回包含Current属性和MoveNext()方法的一个类型,那就可以用foreach
//建立一个有GetEnumerator的类
public class LikeIEnumerable
{
public IEnumerator GetEnumerator()
{
yield return 1;
yield return 2;
yield return 3;
}
}
//然后使用foreach进行测试
public void Test()
{
LikeIEnumerable likeEnumerable = new LikeIEnumerable();
foreach (var item in likeEnumerable)
{
Log.I(item); //打印出来
}
}
//输出结果:
//1
//2
//3
yield关键字是一种语法糖,实际上还是通过实现IEnumberable、IEnumberable< T >、IEnumberator和IEnumberator< T >接口来满足迭代功能
IL阶段下面这部分内容实际上会被生成一个新的类来实现IEnumberable、IEnumberable< T >、IEnumberator和IEnumberator< T >
//类似如下
public class YieldClass : IEnumerator
那上面所述的LikeIEnumerable类这部分内容
{
//dosomething
yield return 1;
//dosomething
yield return 2;
//dosomething
yield return 3;
}
实际上可以被看作
public class LikeYield
{
int state = 0;
private object mCurrent;
public object Current
{
get
{
return mCurrent;
}
}
public bool MoveNext()
{
switch (state)
{
case 0:
state++;
//dosomething
mCurrent = 1;
return true;
case 1:
state++;
//dosomething
mCurrent = 2;
return true;
case 2:
state++;
//dosomething
mCurrent = 3;
return true;
default:
return false;
}
}
}
LikeIEnumerable的迭代部分
foreach (var item in likeEnumerable)
{
Log.I(item);
}
等同于
while(likeYield.MoveNext())
{
var item = likeYield.Current;
Log.I(item);
}
更细致的IL代码可以看这篇文章
c# yield关键字原理详解