我真的需要实施 IEnumerable 吗?什么是 System.Collections.IEnumerator?

Do I REALLY need to implement IEnumerable? What is System.Collections.IEnumerator?

我有两个问题:

第一题:

我编写了一个扫描程序,它根据解析器的需要生成令牌流。

public class Scanner
{
    public IEnumerator<Token> GetEnumerator()
    { //DO SOME STUFF HERE
      yield return new Token(...);
    }
}

public class Parser
{
    IEnumerator<Token> iterator;
    ...
    public Parser(Scanner scanner)
    {
        this.iterator = scanner.GetEnumerator(); 
        ...
    }
    public Token nextToken()
    {
        //generate the next token
        iterator.MoveNext();//there is no need to test MoveNext()
        return iterator.Current;//get the generated token
    }
    ...
}

考虑到这实际上是我在整个框架中使用 Scanner class 的唯一情况,您认为这样可以还是 Scanner 实现 IEnumerable 更正确]?

如果我没记错的话,编译器会做一些操作让 Scanner 实现 IEnumerable,对吗?

第二题:

假设 Scanner 实现了 IEnumerable,那么代码将如下所示:

public class Scanner:IEnumerable<Token>
{
public IEnumerator<Token> GetEnumerator()
{
    ...
}

IEnumerator IEnumerable.GetEnumerator()
{
   return this.GetEnumerator();//I'M NOT SURE OF THIS!
}

我不明白的是IEnumerable.GetEnumerator()是什么意思,我的实现是否正确。我知道这是必要的,因为 Scanner 实现 IEnumerable<T> 而不是 IEnumerable (否则 GetEnumerator() 就足够了,但是在 Current 我应该从对象做一个低效的向下转换到令牌)。

有人能解释一下吗?

IEnumerable<T> 扩展了 IEnumerable,因此如果您正在实施 IEnumerable<T>,您也会自动实施 IEnumerable.

IEnumerable 的存在有一个原因:因为 C# 1.0 没有泛型。因此,我们现在必须一直实施 IEnumerable,这有点烦人。

您的实现是正确的:实现 IEnumerable 时,只需调用通用版本即可。

IEnumerator IEnumerable.GetEnumerator()
{
   return this.GetEnumerator();
}

IEnumerable<T>IEnumerator<T> 不同的目的是表示您尚未开始枚举的一系列值。您可以像值的容器一样存储和传递它,当您想知道它包含什么时,您可以通过调用 GetEnumerator.

开始枚举它

或者更常见的是,您会使用 foreach 循环或 LINQ 方法(例如 SelectWhere 来循环遍历它。这些方法适用于 IEnumerable<T>,不适用于 IEnumerator<T>

如果你只实现 IEnumerator<T> 那么枚举只能发生一次,在向前的方向。要重复枚举,用户必须再次调用您的方法以创建另一个枚举器。

所以 IEnumerable<T> 是 "factory pattern" 的一个实例。这是一家制造 IEnumerator<T>.

的工厂

(关于非泛型 IEnumerable 的奇怪细节,正如@Dennis_E 所说,那只是古老的历史。您转发到泛型版本的实现是完全标准的。)

正如@DmitryBychenko 在评论中所说,IEnumerator<T> 基于 IDisposableforeach 循环负责在其上调用 Dispose。如果序列最终使用 yield return 实现,Dispose 的效果是调用任何尚未执行的 finally 块。