如何在没有 yield 语句的情况下实现迭代器模式 (IEnumerator<T>)
How to implement Iterator pattern (IEnumerator<T>) without the yield statement
如何在不使用 yield
关键字的情况下重写 GetEnumerator
方法?
方法代码:
public IEnumerator<int> GetEnumerator()
{
yield return 1;
Console.WriteLine("1");
yield return 2;
}
我就是想知道怎么手动实现。
其实yield
语句是一个syntactic sugar让编译器实际生成一个class实现了IEnumerator<T>
接口并重写了方法体在状态机中使用 yield
语句。
每个状态都与最终生成序列中下一个元素的一部分代码相关联。这嵌入在 MoveNext()
方法中。状态机可以表示所有必要的构造(序列、选择、迭代),因此所有 C# 代码(方法中的含义语句)都可以这样重写。那是 yield
.
的基础 'magic'
在您的特定情况下,这种重写到状态机和 IEnumerator<T>
(及其继承的 IEnumerator
(非通用)和 IDisposable
接口的相应完整实现将看起来像这样:
public class CustomEnumerator : IEnumerator<int>
{
public int Current { get; private set; }
object IEnumerator.Current => this.Current;
// internal 'position' in the sequence, i.e. the current state of the state machine
private int position = 0;
public bool MoveNext()
{
// advance to next state
// (works for linear algorithms; an alternative is to select the next state at the end of processing the current state)
position++;
// perform the code associated with the current state and produce an element
switch (position)
{
// state 1: line 'yield return 1;'
case 1:
Current = 1;
return true;
// state 2: lines 'Console.WriteLine("1");' and 'yield return 2;'
case 2:
Console.WriteLine("1"); // see also note at the end of this answer
Current = 2;
return true;
// there are no other states in this state machine
default:
return false;
}
}
public void Reset()
{
position = 0;
}
public void Dispose()
{
// nothing to do here
}
}
每次调用 MoveNext()
,即 foreach
语句的每次迭代内部发生的事情,都会导致执行一部分代码,直到生成序列中的下一个元素.
要使此实现可用,需要相应的 IEnumerable<T>
实现,这非常简单:
public class CustomEnumerable : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
return new CustomEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
然后下面的两个 foreach
循环将产生完全相同的结果:
void Main()
{
// custom implementation of IEnumerator<T>
foreach (int i in new CustomEnumerable())
{
Console.WriteLine(i);
}
// your original implementation—will produce same results
// note: I assume someObject implements IEnumerable<T> and hence your GetEnumerator() method
foreach (int i in someObject)
{
Console.WriteLine(i);
}
}
注意:在您的 GetEnumerator()
代码中,对 Console.WriteLine("1");
的调用是 在 枚举器 returns 1
(并且调用者处理它)所以看起来有点奇怪。
如何在不使用 yield
关键字的情况下重写 GetEnumerator
方法?
方法代码:
public IEnumerator<int> GetEnumerator()
{
yield return 1;
Console.WriteLine("1");
yield return 2;
}
我就是想知道怎么手动实现。
其实yield
语句是一个syntactic sugar让编译器实际生成一个class实现了IEnumerator<T>
接口并重写了方法体在状态机中使用 yield
语句。
每个状态都与最终生成序列中下一个元素的一部分代码相关联。这嵌入在 MoveNext()
方法中。状态机可以表示所有必要的构造(序列、选择、迭代),因此所有 C# 代码(方法中的含义语句)都可以这样重写。那是 yield
.
在您的特定情况下,这种重写到状态机和 IEnumerator<T>
(及其继承的 IEnumerator
(非通用)和 IDisposable
接口的相应完整实现将看起来像这样:
public class CustomEnumerator : IEnumerator<int>
{
public int Current { get; private set; }
object IEnumerator.Current => this.Current;
// internal 'position' in the sequence, i.e. the current state of the state machine
private int position = 0;
public bool MoveNext()
{
// advance to next state
// (works for linear algorithms; an alternative is to select the next state at the end of processing the current state)
position++;
// perform the code associated with the current state and produce an element
switch (position)
{
// state 1: line 'yield return 1;'
case 1:
Current = 1;
return true;
// state 2: lines 'Console.WriteLine("1");' and 'yield return 2;'
case 2:
Console.WriteLine("1"); // see also note at the end of this answer
Current = 2;
return true;
// there are no other states in this state machine
default:
return false;
}
}
public void Reset()
{
position = 0;
}
public void Dispose()
{
// nothing to do here
}
}
每次调用 MoveNext()
,即 foreach
语句的每次迭代内部发生的事情,都会导致执行一部分代码,直到生成序列中的下一个元素.
要使此实现可用,需要相应的 IEnumerable<T>
实现,这非常简单:
public class CustomEnumerable : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
return new CustomEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
然后下面的两个 foreach
循环将产生完全相同的结果:
void Main()
{
// custom implementation of IEnumerator<T>
foreach (int i in new CustomEnumerable())
{
Console.WriteLine(i);
}
// your original implementation—will produce same results
// note: I assume someObject implements IEnumerable<T> and hence your GetEnumerator() method
foreach (int i in someObject)
{
Console.WriteLine(i);
}
}
注意:在您的 GetEnumerator()
代码中,对 Console.WriteLine("1");
的调用是 在 枚举器 returns 1
(并且调用者处理它)所以看起来有点奇怪。