10.5 实现类的枚举支持
在第3章中,我们已经看到了如何使用for-in
循环作为经典for循环的替代方法。在那一节中,我描述了如何将for-in
循环用于数组,字符串,集合和一些其他系统数据类型。只要类定义了枚举支持,就可以将这种循环应用于该类。尽管最明显的示例是包含元素列表的类,但从技术角度来看,此功能是相当开放的。
要在Object Pascal 中支持枚举类中的元素,必须添加一个名为 GetEnumerator
的方法,该方法返回一个类(实际的枚举类),并使用 MoveNext
方法和 Current
属性定义这个枚举类——前者用于在元素之间导航,后者用于返回实际的元素。完成这个步骤以后(我将在第二个实际示例中向您展示如何完成),编译器就可以解析for-in循环,其中目标是我们类,单独元素必须是枚举器的Current
属性的相同类型。一旦这样做了(稍后我将在一个实际例子中向你展示如何做),编译器就可以解析一个for-in
循环,其中的目标是我们的类,而各个元素必须与枚举器的Current
属性的类型相同。
虽然说并不是严格必要,但把枚举器支持类作为嵌套类型来实现似乎是个好主意(这是第 7 章中涉及的一种语言特性),因为单独使用用于枚举的特定类型毫无意义。
下面这个类是 NumbersEnumerator
示例的一部分,它存储了一系列数字(一种抽象集合),并允许对它们进行迭代。这是通过定义一个枚举器实现的,枚举器被声明为嵌套类型,并由 GetEnumerator 函数返回:
type
TNumbersRange = class
public
type
TNumbersRangeEnum = class
private
NPos: Integer;
FRange: TNumbersRange;
public
constructor Create(ARange: TNumbersRange);
function MoveNext: Boolean;
function GetCurrent: Integer;
property Current: Integer read GetCurrent;
end;
private
FNStart: Integer;
FNEnd: Integer;
public
function GetEnumerator: TNumbersRangeEnum;
procedure Set_NEnd(const Value: Integer);
procedure Set_NStart(const Value: Integer);
property NStart: Integer read FNStart write Set_NStart;
property NEnd: Integer read FNEnd write Set_NEnd;
end;
GetEnumerator
方法创建了一个嵌套类的对象,用于存储对数据进行迭代的状态信息。
请注意枚举器构造函数是如何保持对实际枚举对象(使用 Self 作为参数传递的对象)的引用,并将初始位置设置为起始位置的:
function TNumbersRange.GetEnumerator: TNumbersRangeEnum;
begin
Result := TNumbersRangeEnum.Create(Self);
end;
constructor TNumbersRange.TNumbersRangeEnum.Create(ARange: TNumbersRange);
begin
inherited Create;
FRange := ARange;
NPos := FRange.NStart - 1; //sets the initial positon to the very beginning
end;
注意 为什么构造函数将初始值设置为第一个值减 1,而不是预想中的第一个值呢?实际上,编译器生成的 for-in 循环的代码相当于:首先创建了一个枚举器(enumerator),然后在
MoveNext
返回True
的情况下执行代码,一直循环直到MoveNext
返回False
,在循环中使用Current
方法获取当前元素的值。测试是在获取第一个值之前进行的,因为列表可能没有值。这意味着MoveNext
会在第一个元素被使用之前被调用。我没有使用更复杂的逻辑来实现这一点,而是简单地将初始值设置为第一个值之前的一个值,这样在第一次调用MoveNext
时,枚举器就会被定位在第一个值上。
最后,枚举器方法可以让你访问数据,并提供一种方法移动到列表中的下一个值(或范围内的下一个元素):
function TNumbersRange.TNumbersRangeEnum.GetCurrent: Integer;
begin
Result := NPos;
end;
function TNumbersRange.TNumbersRangeEnum.MoveNext: Boolean;
begin
Inc(NPos);
Result := NPos <= FRange.NEnd;
end;
正如你在上面的代码中所看到的,MoveNext
方法有两个不同的目的:移动到列表的下一个元素,以及检查枚举器是否已经到达终点(在这种情况下,该方法返回 False)。
完成所有这些工作后,现在就可以使用for-in
循环遍历 range
对象的值了:
输出结果就是介于 10 和 23 之间(包括 10 和 23)的枚举值列表:
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意: 在 RTL(Run-Time Library) 和 VCL(Visual Component Library) 库中,有许多情况下都定义了枚举器,例如,每个
TComponent
都可以枚举它所拥有的组件。目前缺少的是子控件的枚举。在第 12 章的“使用类助手添加枚举”部分,我们将看到如何创建这样一个枚举。之所以这个示例不在这里,是因为我们首先需要讨论类助手(class helpers)。