1

Delphi 枚举器中的规范 MoveNext 是这样编写的:

function TListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FList.Count - 1;
  if Result then
    Inc(FIndex);
end;

这是整个 RTL、VCL 等使用的形式。这种形式似乎在 3 rd方代码中也很普遍。

我认为它可以更简单地写成这样:

function TListEnumerator.MoveNext: Boolean;
begin
  Inc(FIndex);
  Result := FIndex < FList.Count;
end;

为什么不能使用更简单的形式有什么好的理由吗?


我的推理如下。一旦MoveNext返回False,该Current属性将不再被访问。不在列表末尾没关系,FIndex因为它再也不会使用了。for in 循环实际上是这样实现的:

while Enumerator.MoveNext do
  Enumerator.Current.DoSomething;

事实上FIndex,越界对我来说实际上更有意义。这意味着如果有人使用手写的枚举器代码,那么如果在返回Current后访问,他们将得到范围检查错误。MoveNextFalse

更重要的FIndex-1,在第一次调用MoveNext. 这是左边列表中的一个。在最后一次调用 之后MoveNext,返回的那个False不是合适的,FIndex就是Count右边列表中的那个。

4

3 回答 3

4

您假设TListEnumerator只会在for循环中使用。尽管这将是迄今为止最常见的情况,但如果不是,您建议的版本可能会出现异常。MoveNext在它已经返回一次之后再次调用就可以了False。如果你继续调用MoveNext直到它返回True,你必须得到一个无限循环,并且使用你建议的版本,循环不会是无限的,一旦你过去了它就会终止FIndex = MaxInt

请参阅 Delphi 的实现所基于的文档IEnumerator.MoveNext(IIRC,它实际上首先在现已死的 Delphi .NET 中可用):

当枚举数位于此位置时,对 MoveNext 的后续调用也将返回 false,直到调用 Reset。

于 2012-10-20T18:35:55.530 回答
2

纯粹从语义上思考,我会说原始版本是正确的。把它想象成一场对话:

Application: Hey, list. Go to the next item.
List: No, sorry. I can't.
Application: Oh, allright.
  <some time later:>
Application: List, please give me the current item
List: No problem, here you go.


Whereas in your suggestion, it would go like this:


Application, Hey, list. Go to the next item.
List: No, that's impossible.
Application: Oh, all right.
  <some time later:>
Application: Hi again, List. Give me the current item.
List: Here you go. 
Application: Thanks, mate.
  <some time later, when using the item:>
Application: What the ????!
于 2012-10-20T18:19:44.967 回答
1

有趣的问题(+1)。如果不了解枚举器的所有可能用途(或根本不知道它的用途),乍一看,它的功能在列表的开头和结尾之间有所不同,这确实看起来很奇怪。正如您所说:在开始时Current导致List index out of bounds错误,那么为什么不在最后呢?似乎不一致。

但再想一想:一旦创建了一个枚举器,由什么或谁创建,列表的边界是未知的,所以第一个元素不是也不能被选择。这与边界已知且索引受限时的最终行为相比。

数据集的功能完全相同,但枚举器的实现可能会受益于BOF- 和 -EOF类似的行为。

于 2012-10-21T09:46:58.517 回答