本文严重参考了 笔记:IEnumerable和IEnumerator(包括泛型版), 另外caozhy在BBS也有一段总结(链接地址), 可以参考
一个GetEnumerator()方法, 无参数,返回类型任意
Current属性,只有get方法,不能有set方法(编译器也不允许),返回类型任意
MoveNext()方法,无参数, 判断是否到遍历完毕,返回类型bool
为了让Current属性知道应该返回哪个值了, 和 辅助MoveNext判断是否遍历完毕, 通常还会有个 int index, 虽然不必须但很有必要
其实接口的本质就是让编译器去绑定一个对象的方法,在这里,C#编译器的确不依赖接口
//手动让一个int数组支持foreach
class A
{
int[] array = new int[]{1,2,3,4,5};
int index=-1;
public A GetEnumerator() { return this; }
public int Current { get { return array[index]; } }
public bool MoveNext() { index++; return index < array.Length ? true : false; }
}
class TaskThread
{
static void Main()
{
A a = new A();
foreach (var s in a)
Console.WriteLine(s);
}
}
IEnumerator接口指明需要Current、MoveNext(), 但VS中点击 "实现接口", 通常还会生成Reset()方法.
一般我们对一个数组变量连续2次使用 foreach, 就会循环两遍; 如果你仅实现了上面的三个方法和属性, 连续2次foreach却不能达到要求
class A
{
int[] array = new int[]{1,2,3,4,5};
int index=-1;
public A GetEnumerator() { return this; }
public int Current { get { return array[index]; } }
public bool MoveNext()
{
index++;
if (index < array.Length)
return true;
else
{
//Reset();
return false;
}
}
//private void Reset()
//{
// index = -1;
//}
}
class TaskThread
{
static void Main()
{
A a = new A();
foreach (var s in a)
Console.WriteLine(s);
Console.WriteLine("循环第二次");
foreach (var s in a)
Console.WriteLine(s);
}
}
当把注释掉的 Reset() 方法恢复后, 就可以得到正常结果了.
4. IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接,可以通过IEnumerable的GetEnumerator()得到IEnumerator对象
那为什么集合类不直接继承(支持)IEnumerator<T>和IEnumerator接口?
假如同时有两个循环交错遍历同一个集合(一个foreach嵌套了另外一个foreach),那么集合必须维持当前元素的一个状态指示器,确保当调用MoveNext()方法时,能正确定位下一个元素。
为了解决这个问题,集合类不直接支持IEnumerator<T>和IEnumerator接口。而是通过IEnumerable的GetEnumerator返回一个新的IEnumerator对象来负责维护循环遍历的状态,IEnumerator(迭代器,或者叫枚举数)相当于一个“游标”(cursor)或者“书签”。可以有多个书签,移动每个书签都可独立于其他书签来遍历集合
下面的代码是双线程同时访问集合
class A
{
int[] array = new int[]{1,2,3,4,5};
int index=-1;
public A GetEnumerator() { return this; }
public int Current { get { return array[index]; } }
public bool MoveNext()
{
index++;
if (index < array.Length)
return true;
else
{
Reset();
return false;
}
}
private void Reset()
{
index = -1;
}
}
class TaskThread
{
static A a = new A();
static void Main()
{
Thread m = new Thread(GetSum);
Thread n = new Thread(GetSum);
m.Start();
n.Start();
}
static void GetSum()
{
int sum = 0;
foreach (var s in a)
{
Thread.Sleep(100); //模拟大量运算, 否则看不出效果
sum += s;
}
Console.WriteLine("ThreadID={0}, sum={1}",Thread.CurrentThread.ManagedThreadId,sum);
}
运行的结果是
ThreadID=3, sum=5
ThreadID=4, sum=26
按理正常结果应该是 15, 可以看出两个线程都分别干扰了对方的index
5. 由于IEnumerable<T>扩展(继承)了旧的IEnumerable接口,所以要实现两个不同的方法:
IEnumerator<T> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator; // 由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现
6. 我们通过ILSpy 看看源码中 List 的IEnumerator定义
public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
[__DynamicallyInvokable]
public T Current
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.current;
}
}
[__DynamicallyInvokable]
object IEnumerator.Current
{
[__DynamicallyInvokable]
get
{
if (this.index == 0 || this.index == this.list._size + 1)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return this.Current;
}
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default(T);
}
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public void Dispose()
{
}
[__DynamicallyInvokable]
public bool MoveNext()
{
List<T> list = this.list;
if (this.version == list._version && this.index < list._size)
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
private bool MoveNextRare()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
[__DynamicallyInvokable]
void IEnumerator.Reset()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = 0;
this.current = default(T);
}
}