目录
1.非泛型的IEnumerator 和 IEnumerable 的实现:
.NET中 通过 IEnumerator 和 IEnumerable 以及他们的泛型等价物 来封装 Iterator 模式。
迭代器模式:允许你访问一个数据项序列,而无需关心序列内部的组织结构。
1.非泛型的IEnumerator 和 IEnumerable 的实现:
// 枚举器接口 IEnumerator
public interface IEnumerator
{
object Current { get; }
// 如果是返回 false,就是结束迭代器块
bool MoveNext();
void Reset();
}
// 可枚举的接口 IEnumerable, 返回枚举器接口
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
在上述两个接口在foreach中的运用,foreach其实是GoF Iterator模式的实现。
foreach(var item in dict)
或者
// dict是实现了 IEnumerable 接口的可迭代对象。如果不用foreach,可以像下面这样迭代。
var iterator = dict.GetEnumerator();
while(iterator.MoveNext())
{
item = iterator.Current;
.....
}
2.C#1举例如何实现可迭代对象:
IterationSample 类为可迭代对象,需要继承IEnumerable接口,需要实现:
一个获得枚举器的函数,GetEnumerator函数
一个枚举器类,IterationSampleIterator
class IterationSample : IEnumerable
{
object[] values;
int startingPoint;
public IterationSample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
public IEnumerator GetEnumerator()
{
return new IterationSampleIterator(this);
}
// 迭代器实现
class IterationSampleIterator : IEnumerator
{
// 重要数据成员,来获取当前值,下一个值,是否结束。在构造函数中赋值。
IterationSample parent;
// 标记当前所迭代的位置。
int position;
internal IterationSampleIterator(IterationSample parent)
{
this.parent = parent;
position = -1;
}
public bool MoveNext()
{
if (position != parent.values.Length)
{
position++;
}
return position < parent.values.Length;
}
public object Current
{
get
{
if (position == -1 ||
position == parent.values.Length)
{
throw new InvalidOperationException();
}
int index = (position + parent.startingPoint);
index = index % parent.values.Length;
return parent.values[index];
}
}
public void Reset()
{
position = -1;
}
}
}
这样就能像下面这样进行迭代:
// sample 是 IterationSample 对象
var iterator = sample.GetEnumerator();
while(iterator.MoveNext())
{
item = iterator.Current;
.....
}
或者用
foreach(var item in sample)
{
.....
}
实现迭代器的代码容易理解,但是实现代码繁琐。
3.C#2举例实现:使用yield关键字简化
同样实现上述例子中完的功能:
GetEnumerator函数重新实现,包含yield return 语句。
IterationSampleIterator 枚举器类 删除不需要了。
class IterationSample : IEnumerable
{
object[] values;
int startingPoint;
public IterationSample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
public IEnumerator GetEnumerator()
{
for (int index = 0; index <values.Length; index++)
{
yield return values[(index + startingPoint) % values.Length];
}
}
}
yield关键字是一个语法糖,背后其实生成了一个新的类。
具体实现原理可以看下面这篇文章,详细易懂:https://www.cnblogs.com/blueberryzzz/p/8678700.html#undefined
更详细的可以看这个例子(来自这里):
原始的Test代码:
using System;
using System.Collections;
class Test
{
static IEnumerator GetCounter()
{
for (int count = 0; count < 10; count++)
{
yield return count;
}
}
}
真正的Test类:C#编译器针对包含yield的函数生成了一个新的类 <GetCounter>d__0
internal class Test
{
// Note how this doesn't execute any of our original code
private static IEnumerator GetCounter()
{
return new <GetCounter>d__0(0);
}
// Nested type automatically created by the compiler to implement the iterator
[CompilerGenerated]
private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable
{
// Fields: there'll always be a "state" and "current", but the "count"
// comes from the local variable in our iterator block.
private int <>1__state;
private object <>2__current;
public int <count>5__1;
[DebuggerHidden]
public <GetCounter>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
}
// Almost all of the real work happens here
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<count>5__1 = 0;
while (this.<count>5__1 < 10) //这里针对循环处理
{
this.<>2__current = this.<count>5__1;
this.<>1__state = 1;
return true;
Label_004B:
this.<>1__state = -1;
this.<count>5__1++;
}
break;
case 1:
goto Label_004B;
}
return false;
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
}
}
可以看出,其实就是把 原始函数里的内容,都放入到了MoveNext中了。
类似的,也可以看下面这个例子(来自深入理解C#):
CreateEnumerable 并没有实现GetEnumerator,以及实现MoveNext 和 Current函数,但是我们却可以在Main函数中,进行调用,说明其实是编译器给你生成了一个实现了这些接口的类。
(如果是CreateEnumerable返回 IEnumerable,编译器实现的类应该是继承自IEnumerator 和 IEnumeratable
如果是CreateEnumerable返回 IEnumerator,编译器实现的类应该是只继承自IEnumerator
)
class IteratorWorkflow
{
static readonly string Padding = new string(' ', 30);
// 也可以返回 IEnumerator<int>,相应的Main里面就不需要先获取 iterable 了。
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 代码就停止执行,再下一次调用MoveNext的时候 继续执行。
yield return i;
Console.WriteLine("{0}After yield", Padding);
}
Console.WriteLine("{0}Yielding final value", Padding);
yield return -1;
Console.WriteLine("{0}End of GetEnumerator()", Padding);
}
static void Main()
{
// 此处不会真正调用CreateEnumerable()函数
IEnumerable<int> iterable = CreateEnumerable();
IEnumerator<int> iterator = iterable.GetEnumerator();
Console.WriteLine("Starting to iterate");
// 也可以用foreach来替代。这里展示了foreach的类似过程。
while (true)
{
Console.WriteLine("Calling MoveNext()...");
// 此处开始真正调用CreateEnumerable()函数
bool result = iterator.MoveNext();
Console.WriteLine("... MoveNext result={0}", result);
if (!result)
{
break;
}
Console.WriteLine("Fetching Current...");
Console.WriteLine("... Current result={0}", iterator.Current);
}
}
}
自己运行一下上述代码,看输出信息就知道了 编译器自动生成的迭代器的 工作原理了。
关于yield:
1.可以在方法,属性 和 索引器中使用 yield 来实现迭代器。 使用yield,函数返回类型必须是 IEnumberable<T>、IEnumberable、IEnumberator<T>和IEnumberator中的一个。函数参数不能是 ref 或 out
2. try 和 catch 语句里面不能出现yield return
3.不能在匿名方法中用迭代器代码块,也就是yield。
4.yield break 相当于普通方法中的return
class YieldBreak
{
static IEnumerable<int> CountWithTimeLimit(DateTime limit)
{
for (int i = 1; i <= 100; i++)
{
if (DateTime.Now >= limit)
{
// 大于2s,迭代块就退出了
yield break;
}
yield return i;
}
}
static void Main()
{
DateTime stop = DateTime.Now.AddSeconds(2);
foreach (int i in CountWithTimeLimit(stop))
{
Console.WriteLine("Received {0}", i);
Thread.Sleep(300);
}
}
}
5.finally块 被 实现为 Dispose
应该是编译器实现的类 实现了 IDisposable接口 上述链接中的例子也是这样,所以退出迭代器块的时候,就会调用Dispose函数。
class YieldBreakAndTryFinally
{
static IEnumerable<int> CountWithTimeLimit(DateTime limit)
{
try
{
for (int i = 1; i <= 100; i++)
{
if (DateTime.Now >= limit)
{
yield break;
}
yield return i;
}
}
finally
{
// 停止使用迭代器块会执行finally,即Dispose的调用会触发finally块的执行。
Console.WriteLine("Stopping!");
}
}
static void Main()
{
DateTime stop = DateTime.Now.AddSeconds(2);
foreach (int i in CountWithTimeLimit(stop))
{
Console.WriteLine("Received {0}", i);
if(i > 3)
{
return;
}
Thread.Sleep(300);
}
}
}
6.实际开发中实现迭代器的例子
1.timetable 是一个时刻表的类。
方法一:用for循环 来表示时刻表中的每一天。
for (DateTime day = timetable.StartDate; day <= timetable.EndDate; day = day.AddDays(1))
{}
方法二:实现迭代器,用foreach来表示时刻表中的每一天。
foreach (var day in timetable.DateRange)
{
}
// 需要实现DateRange是一个可迭代的集合,timetable类中的一个属性。
public IEnumerable <DateTime> DateRange
{
get
{
// 原始的代码被放到了这里。
for (DateTime day = timetable.StartDate; day <= timetable.EndDate; day = day.AddDays(1))
{
yield return day;
}
}
}
2.迭代文件中的行
原始方法:每次读文件中的行都需要的写如下的类似代码,繁琐
// reader阅读器 需要释放
using(TextReader reader = File.OpenText(filename))
{
string line;
while((line = reader.ReadLine())!= null)
{
//针对line进行某些处理。
}
}
改进方法:基础迭代器版本
foreach (string line in ReadLines("test.txt"))
{
//针对line进行某些处理。
}
public static IEnumerable<string> ReadLines(string filename)
{
// using 扮演了 try/finally 块的角色。具体实现了什么呢?
// 在到达文件末尾或在中途调用 IEnumerabtor<string>.Dispose方法时,将进入finally块。
// 同样,是把原始的代码移动到这里
using (TextReader reader = File.OpenText(filename))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
改进迭代器版本:
如果不是打开文本获取阅读器而是通过网络流读取文本,或者不是用utf8编码格式,应该怎么扩展
(通过网络流读取文本 是怎么样的?)
所以直接传入 TextReader参数 不好,这里传入一个 Func<TextReader> 类型的参数。
public static IEnumerable<string> ReadLines(Func<TextReader> provider)
{
//同样调用了 using
using (TextReader reader = provider())
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
// 调用上面
public static IEnumerable<string> ReadLines(string filename, Encoding encoding)
{
// 复习:使用了匿名方法,捕获所在方法的参数。
return ReadLines(delegate
{
return File.OpenText(filename, encoding);
});
}
// 调用上面,默认使用utf8
public static IEnumerable<string> ReadLines(string filename)
{
return ReadLines(filename, Encoding.UTF8);
}
参考:
《深入理解C#》
https://www.cnblogs.com/blueberryzzz/p/8678700.html#undefined