c# - 实现 IEnumerable<T> 和 IEnumerator<T> 时 GetEnumerator() 的推荐行为

标签 c# .net ienumerable

我正在实现我自己的可枚举类型。类似这样的东西:

public class LineReaderEnumerable : IEnumerable<string>, IDisposable
{
    private readonly LineEnumerator enumerator;

    public LineReaderEnumerable(FileStream fileStream)
    {
        enumerator = new LineEnumerator(new StreamReader(fileStream, Encoding.Default));
    }

    public IEnumerator<string> GetEnumerator()
    {
        return enumerator;
    }

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

    public void Dispose()
    {
       enumerator.Dispose();
    }
}

枚举器类:

public class LineEnumerator : IEnumerator<string>
{
    private readonly StreamReader reader;
    private string current;

    public LineEnumerator(StreamReader reader)
    {
        this.reader = reader;
    }

    public void Dispose()
    {
        reader.Dispose();
    }

    public bool MoveNext()
    {
        if (reader.EndOfStream)
        {
            return false;
        }
        current = reader.ReadLine();
        return true;
    }

    public void Reset()
    {
        reader.DiscardBufferedData();
        reader.BaseStream.Seek(0, SeekOrigin.Begin);
        reader.BaseStream.Position = 0;
    }

    public string Current
    {
        get { return current; }
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }
}

我的问题是:当调用 GetEnumerator() 时,我应该在枚举器上调用 Reset() 还是调用方法(如 foreach)负责执行此操作?

GetEnumerator() 应该创建一个新实例,还是应该始终返回相同的实例?

最佳答案

您的模型已基本损坏 - 您应该创建一个新的 IEnumerator<T>每次GetEnumerator()叫做。迭代器应该相互独立。例如,我应该能够写:

var lines = new LinesEnumerable(...);
foreach (var line1 in lines)
{
    foreach (var line2 in lines)
    {
        ...
    }
}

基本上得到文件中每一行与其他每一行的叉积。

这意味着 LineEnumerable类不应该被赋予FileStream - 应该给它一些可以用来获得 FileStream 的东西每次你需要一个,例如一个文件名。

例如,您可以使用迭代器 block 在单个方法调用中完成所有这些操作:

// Like File.ReadLines in .NET 4 - except that's broken (see comments)
public IEnumerable<string> ReadLines(string filename)
{
    using (TextReader reader = File.OpenText(filename))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

然后:

var lines = ReadLines(filename);
// foreach loops as before

...那会很好的。

编辑:请注意,某些序列自然只能迭代一次 - 例如网络流,或来自未知种子的随机数序列。

这样的序列确实更好地表示为 IEnumerator<T>而不是 IEnumerable<T> ,但这使得使用 LINQ 进行过滤等变得更加困难。 IMO 这样的序列应该至少在第二次调用 GetEnumerator() 时抛出异常- 返回同一个迭代器两次是一个非常糟糕的主意。

关于c# - 实现 IEnumerable<T> 和 IEnumerator<T> 时 GetEnumerator() 的推荐行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7673161/

相关文章:

.net - 哪些报告框架或报告控件可以与 Mono.net 一起使用?请列出它们的名称?

c# - 使用 Dapper 和 Npgsql 无法从 Guid 列表中进行选择

.net - 用简短的语言解释为什么需要 IQueryable<T>

javascript - 访问 javascript 端 POST 请求返回的 IEnumerable (Web API)

c# - 包含 '{' 字符的 WriteLine 字符串在 C# 中抛出 FormatException

c# - 关闭套接字,然后从 .net 中的同一端口重新打开它

c# - 为什么我的 WebClient 上传文件代码挂起?

c# - 从同一台机器上的另一个进程接收任务<T>

c# - T4MVC 无法使用 ErrorGeneratingOutput 生成类

c# - 使用 EntityFramework 6 和 Linq 批量更新对象列表