以下是一個傳統且結實的 Enumerable & Enumerator 範例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ConsoleApplication1 {
class Program {
class MyEnumerable : IEnumerable <int >, IEnumerable {
private int[] values;
private int offset;
public MyEnumerable( int[] values, int offset) {
this.values = values;
this.offset = offset;
}
public IEnumerator<int > GetEnumerator() {
return new MyIterator(this );
}
IEnumerator IEnumerable .GetEnumerator() {
return this.GetEnumerator();
}
private class MyIterator : IEnumerator <int >, IEnumerator {
private MyEnumerable target;
private int pos;
public MyIterator( MyEnumerable target) {
this.target = target;
this.pos = -1;
}
public bool MoveNext() {
if( this.pos != this.target.values.Length) this.pos++;
return this.pos < this.target.values.Length;
}
public int Current {
get {
if( this.pos == -1 || this.pos == this.target.values.Length) {
throw new InvalidOperationException();
}
int index = this.pos + this.target.offset;
index = index % this.target.values.Length;
return this.target.values[index];
}
}
object IEnumerator.Current {
get {
return this.Current;
}
}
public void Reset() { this.pos = -1; }
void IEnumerator.Reset() { this .Reset(); }
public void Dispose() { }
}
}
static void Main( string[] args) {
MyEnumerable list = new MyEnumerable (new int [] {1,2,3,4,5,6}, 2);
foreach( int i in list) Console.WriteLine(i);
Console.WriteLine( "-------------------------");
foreach( object o in (( IEnumerable)list)) Console .WriteLine(o);
Console.WriteLine( "-------------------------");
IEnumerator<int > iterator = list.GetEnumerator();
while(iterator.MoveNext()) Console.WriteLine(iterator.Current.ToString());
(iterator as IDisposable).Dispose();
Console.ReadKey();
}
}
}
自從 C# 2.0 之後, 上面的程式可以被簡化成下面這樣
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ConsoleApplication1 {
class Program {
class MyEnumerable : IEnumerable <int >, IEnumerable {
private int[] values;
private int offset;
public MyEnumerable( int[] values, int offset) {
this.values = values;
this.offset = offset;
}
public IEnumerator<int > GetEnumerator() {
for( int index = 0; index < values.Length; index++) {
yield return values[(index + this.offset) % this.values.Length];
}
}
IEnumerator IEnumerable .GetEnumerator() {
return this.GetEnumerator();
}
}
static void Main( string[] args) {
MyEnumerable list = new MyEnumerable (new int [] {1,2,3,4,5,6}, 2);
foreach( int i in list) Console.WriteLine(i);
Console.WriteLine( "-------------------------");
foreach( object o in (( IEnumerable)list)) Console .WriteLine(o);
Console.WriteLine( "-------------------------");
IEnumerator<int > iterator = list.GetEnumerator();
while(iterator.MoveNext()) Console.WriteLine(iterator.Current.ToString());
(iterator as IDisposable).Dispose();
Console.ReadKey();
}
}
}
實際上發生的事情是:
當 Compiler 看到 yield return 時,
它會自動地幫我們產生一個 Enumerator class,
就像我們之前自己寫的那樣!!!
不過, 有幾個注意事項, 首先觀察下面程式碼的輸出:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1 {
class Program {
private static readonly String padding = new String( ' ', 30);
private static IEnumerable<int > CreateEnumerable() {
Console.WriteLine( "{0}Start of CreateEnumerable()" , padding);
for( int i = 0; i < 3; i++) {
Console.WriteLine( "{0}About to yield {1}", padding, i);
yield return i;
Console.WriteLine( "{0}After yield", padding);
}
Console.WriteLine( "{0}End of CreateEnumerable()", padding);
}
static void Main( string[] args) {
IEnumerable<int > iterable = CreateEnumerable();
IEnumerator<int > iterator = iterable.GetEnumerator();
Console.WriteLine( "Starting to iterate");
while( true) {
Console.WriteLine( "Calling MoveNext()...");
bool result = iterator.MoveNext();
Console.WriteLine( "... MoveNext result={0}", result);
if(!result)
break;
Console.WriteLine( "Fetching Current...");
Console.WriteLine( "... Current result={0}", iterator.Current);
}
Console.ReadKey();
}
}
}
因為 Compiler 動了很大的手腳,
所以上面那隻程式的輸出會讓人很意外!!!
有興趣的話可以使用 Reflector 或 ILDasm 來觀察 Compiler 究竟做了甚麼事.
使用 yield return 來產生 Enumerator/Enumerable 時,
有幾個特別的事項要提醒:
1. code 會在第一次 MoveNext() 時才被執行
2. code 裏頭可以有 try 以及 finally 區塊, 但不可以有 catch 區塊
3. finally 區塊實際上會被移至 Enumerator 的 Dispose() 裡
4. 在第一次 MoveNext() 被呼叫前, Current 會是 default(T)
5. 當 MoveNext() 回傳 false 後, Current 永遠會是最後值
6. 呼叫 Reset() 會丟例外
沒有留言:
張貼留言