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

Evr*_*glu 7 .net c# 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

枚举器类:

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:我应该在调用GetEnumerator()时调用枚举器上的Reset(),还是调用方法(如foreach)的责任?

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

Jon*_*eet 7

您的模型从根本上被打破 - 您应该IEnumerator<T>每次GetEnumerator()调用时创建一个新模型.迭代器意味着彼此独立.例如,我应该能够写:

var lines = new LinesEnumerable(...);
foreach (var line1 in lines)
{
    foreach (var line2 in lines)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

并且基本上得到文件中每一行与每个其他行的交叉积.

这意味着LineEnumerable类应给予FileStream-它应该给予一些东西,可以用来获得一个FileStream你需要一个各一次,如文件名.

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

// 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

var lines = ReadLines(filename);
// foreach loops as before
Run Code Online (Sandbox Code Playgroud)

......那会很好.

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

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


Ric*_*ard 5

您的类型的用户的期望是GetEnumerator()返回新的枚举器对象.

正如您已定义的那样,每次调用都会GetEnumerator返回相同的枚举器,因此代码如下:

var e1 = instance.GetEnumerator();
e1.MoveNext();
var first = e1.Value();

var e2 = instance.GetEnumerator();
e2.MoveNext();
var firstAgain = e2.Value();

Debug.Assert(first == firstAgain);
Run Code Online (Sandbox Code Playgroud)

不会按预期工作.

(内部调用Reset将是一个不寻常的设计,但这是次要的.)

附加: PS 如果你想要一个文件行的枚举器然后使用File.ReadLines,但它出现(见Jon Skeet答案的评论)这会遇到与你的代码相同的问题.