作者easy0519 (Easy)
看板C_Sharp
标题[心得] IEnumerator, IEnumerable, and Yield
时间Mon Mar 28 00:56:13 2016
我来讨鞭,以下是我在 C# 使用 IEnumerator, IEnumerable, and Yield 的整理心得,
目前无广告无音乐网志版本
http://goo.gl/d7fimi
---
这篇是因为工作上同事对於 yield return 的疑问,所以开始写这篇文章,顺便整理一下
自己的经验并且复习。
IEnumerator,迭代器,是一个巡回容器的介面:
// namespace System.Collections
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
使用方法很简单,在一个回圈中呼叫 MoveNext,检查是否还有可列举的元素,利用
Current 来取得目前的元素(Element):
for (var itr = something.GetEnumerator(); itr.MoveNext(); )
{
Console.Write(itr.Current);
}
此外有泛型(Generic)的迭代器 IEnumerator,需多实做用於释放资源的 Dispose 的介
面:
// namespace System.Collections.Generic
using System.Collections;
public interface IEnumerator : IDisposable, IEnumerator
{
T Current { get; }
bool MoveNext();
void Reset();
void Dispose();
}
使用上,建议在列举完元素後,呼叫 Dispose 来释放迭代器的资源,可以使用 using 使
得程式码更易读:
using (var itr = something.GetEnumerator())
{
while (itr.MoveNext())
{
Console.Write(itr.Current);
}
}
IEnumerable,提供一个取得迭代器方法,用该迭代器来列举元素:
// namespace System.Collections
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
另有定义较常使用的泛型的版本:
// namespace System.Collections.Generic
using System.Collections;
public interface IEnumerable : IEnumerable
{
IEnumerator GetEnumerator();
}
在 C# 中,阵列 (Array) 以及大多的容器 (System.Collections 或是
System.Collections.Generic) 都有实做 IEnumerable 介面,意思是可以透过
GetEnumerator() 来列举该容器中的元素。例如 List、int[] 或是 string,都可以使
用其迭代器来列举容器中的元素。
以下是使用 C# 语法 foreach 来列举 List 容器中的元素:
List numbers = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (var number in numbers)
{
Console.Write(number); // Output: 0123456789
}
该 foreach 在编译实作上,使用 IEnumerable 来取得 IEnumerator,透过迭代器列举元
素,可转换程式码如下:
List numbers = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
using(var itr = numbers.GetEnumerator())
{
while (itr.MoveNext())
{
var number = itr.Current;
Console.Write(number); // Output: 0123456789
}
}
也可以自订不同的迭代器,来制作出自定的列举方法,例如下面定义对於 List 容器的
Reverse 迭代器,将元素顺序反转输出:
struct ReverseListEnumerable<T> : IEnumerable<T>
{
List<T> data;
public ReverseListEnumerable(List<T> data)
{
this.data = data;
}
public IEnumerator<T> GetEnumerator()
{
return new Enumerator(this.data);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
struct Enumerator : IEnumerator<T>
{
int index;
List<T> data;
public Enumerator(List<T> data)
{
this.data = data;
this.index = data.Count;
}
public T Current
{
get
{
return this.data[this.index];
}
}
object IEnumerator.Current
{
get
{
return this.Current;
}
}
public void Dispose()
{
// 这里把 data 设定 null,当作释放资源,之後就无法再使用
this.data = null;
}
public bool MoveNext()
{
this.index -= 1;
return this.index >= 0;
}
public void Reset()
{
// 重置 Index
this.index = this.data.Count;
}
}
}
List<int> numbers = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach(var number in new ReverseListEnumerable<int>(numbers))
{
Console.Write(number); // Output: 9876543210
}
没错!自定义迭代器怎麽可以这麽又臭又长…,但 C# 提供一项语法,可以让上述自定义
迭代器的程式码,变得相当容易撰写又好阅读,那就是 yield。
Yield
上节的 Reverse 例子使用 yield 可以改成:
IEnumerable<T> ReverseListEnumerable<T>(List<T> data)
{
for (var index = data.Count - 1; index >= 0; index--)
{
yield return data[index];
}
}
List<int> numbers = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach(var number in ReverseListEnumerable(numbers))
{
Console.Write(number); // Output: 9876543210
}
没有搞错,就是如此简单明了,C# 编译器会把它处理成上节的那一个样子,如果有兴趣
可以参考 Reference 的文章连结 The implementation of iterators in C# and its
consequences。
当第一次呼叫迭代器的 MoveNext 时,程式会一直执行,直到 yield return,此时
yield return 的元素回传,并保留程式位置以及状态,当下一次呼叫 MoveNext() 时,
会从该保留位置以及状态继续执行。
此外也可以使用 yield break 强制中断迭代器列举。下面例子,当列举元素为 3 的倍数
时就中断迭代:
IEnumerable<int> MyEnumerable(IEnumerable<int> enumerable)
{
foreach (var itr in enumerable)
{
if (itr % 3 == 0)
{
yield break;
}
yield return itr;
}
}
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach(var number in MyEnumerable(numbers))
{
Console.Write(number); // Output: 12
}
延伸阅读 System.Linq
前两节中,范例实做 List 容器中 Reverse 迭代器,但这功能在 System.Linq 中已经有
所实做。System.Linq 提供许多对於 IEnumerable 物件的迭代器处理扩充,有哪些可以
使用,可以参考官方文件。以下是使用 System.Linq 来完成 Reverse 功能:
using System.Collections.Generic;
using System.Linq;
List numbers = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach(var number in numbers.Reverse())
{
Console.Write(number); // Output: 9876543210
}
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 211.23.144.23
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_Sharp/M.1459097776.A.528.html
1F:推 neo5277: 推 03/28 01:10
2F:推 Litfal: 说到这个怎麽可以不提 Iterator Pattern 呢XD? 03/28 04:00
感谢你的建议,这部分我在写的过程中确实是没有想到,之後研究後再补充上去
※ 编辑: easy0519 (60.251.127.173), 03/28/2016 11:02:39
3F:推 james732: 推 03/28 11:08