实现 IEnumerable时 GetEnumerator() 的推荐行为和 IEnumerator
我正在实现我自己的枚举类型。类似这样的东西:
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() 应该创建一个新实例,还是应该始终返回相同的实例?
I am implementing my own enumerable type. Something ressembling this:
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();
}
}
The enumerator class:
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; }
}
}
My question is this: should I call Reset() on the enumerator when GetEnumerator() is called or is it the responsability of the calling method (like foreach) to do it?
Should GetEnumerator() create a new one, or is it supposed to always return the same instance?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您的模型从根本上被破坏了 - 每次调用
GetEnumerator()
时,您都应该创建一个新的IEnumerator
。迭代器应该是相互独立的。例如,我应该能够编写:并基本上获得文件中每一行与其他每一行的叉积。
这意味着
LineEnumerable
类不应该被赋予一个FileStream
- 它应该被赋予一些可用于获取的东西每次需要一个FileStream
时,例如文件名。例如,您可以使用迭代器块在单个方法调用中完成所有这些操作:
Then:
...这将很好地工作。
编辑:请注意,某些序列自然只能迭代一次 - 例如网络流或来自未知种子的随机数序列。
此类序列确实更好地表示为
IEnumerator
而不是IEnumerable
,但这使得使用 LINQ 进行过滤等变得更加困难。 IMO 这样的序列应该至少在第二次调用GetEnumerator()
时抛出异常 - 两次返回相同的迭代器是一个非常糟糕的主意。Your model is fundamentally broken - you should create a new
IEnumerator<T>
each timeGetEnumerator()
is called. Iterators are meant to be independent of each other. For example, I ought to be able to write:and basically get the cross-product of each line in the file against each of the other lines.
This means
LineEnumerable
class should not be given aFileStream
- it should be given something which can be used to obtain aFileStream
each time you need one, e.g. a filename.For example, you can do all of this in a single method call using iterator blocks:
Then:
... that will work fine.
EDIT: Note that certain sequences are naturally iterable only once - e.g. a network stream, or a sequence of random numbers from an unknown seed.
Such sequences are really better expressed as
IEnumerator<T>
rather thanIEnumerable<T>
, but that makes filtering etc with LINQ harder. IMO such sequences should at least throw an exception on the second call toGetEnumerator()
- returning the same iterator twice is a really bad idea.您类型的用户的期望是
GetEnumerator()
返回一个新的枚举器对象。正如您所定义的,每次调用
GetEnumerator
都会返回相同的枚举器,因此类似以下的代码将无法按预期工作。
(对
Reset
的内部调用将是一种不寻常的设计,但这在这里是次要的。)附加: PS 如果您想要一个枚举器文件的然后使用
File.ReadLines
,但它出现了(请参阅 Jon Skeet的回答)这也遇到了同样的问题作为你的代码。The expectation of a user of your type would be that
GetEnumerator()
returns a new enumerator object.As you have defined it every call to
GetEnumerator
returns the same enumerator, so code like:will not work as expected.
(An internal call to
Reset
would be an unusual design, but that's secondary here.)Additional: PS If you want an enumerator over the lines of a file then use
File.ReadLines
, but it appears (see comments on Jon Skeet's answer) this suffers from the same problem as your code.如果返回相同的实例,那么第二次迭代将从第一次迭代所在的点返回结果,并且如果代码交替或并行执行,则它们都会相互干扰。所以不,你不应该返回相同的实例。
用于重置
http:// msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
If you return the same instance then the second iteration will be returning results from the point where the first iteration is and both of them will interfere with each other if the code is executing alternatively or in parallel. so No you shouldn't return same instance.
For Reset
http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
这就是调用方法的职责;但是,如果您的枚举器在第一次调用 Reset() 之前无效,您当然应该在返回它之前调用它(这将是一个实现细节)。
在正常操作中,枚举器实际上不会被重置。您可以通过从重置中抛出 NotSupportedException 来验证这一点。
是的,它应该始终返回一个新实例。可以这样想:
Enumerable
是您可以枚举的东西。Enumerator
是您用来枚举的事物。如果 GetEnumerator() 总是返回相同的实例,则包含的类将不是“可枚举的”,而只是知道如何“枚举”(IOW:它只是IHasEnumerator
而不是IEnumerable)
That is the responsability of the calling method; However if your enumerator is invalid before a first call to Reset() you should of course call it before returning it (that would be an implementation detail).
In normal operation, an enumerator is never actually reset. You can verify that by throwing NotSupportedException from within reset.
Yes it should always return a new instance. Think of it this way: an
Enumerable
is something that you can enumerate.Enumerator
is the thing that you use to enumerate with. If GetEnumerator() always returned the same instance, the containing class would not be 'enumerable' but just know how to 'enumerate' (IOW: it would just beIHasEnumerator
instead ofIEnumerable
)就我而言,这应该是打电话者的责任。这遵循 POLA(最小惊讶原则,如果你愿意的话。事实上,你不 ,如果消费者只想枚举从流中的某个点开始的行怎么办?
不希望你的读者控制太多。考虑一下 在尝试查找之前,您应该真正检查一下该流是否实际上是可查找的——许多流不是(例如网络流)。
As far as I'm concerned, it should be the responsibility of the caller. This follows from POLA (the principle of least astonishment, if you like. And indeed, you don't want your reader to control too much. Consider, what if the consumer wants only to enumerate lines from a certain point in the stream onwards?
And regarding the
Reset
method itself, you should really check to see if the stream is actually seekable before trying to seek -- many streams aren't (e.g. network streams).