IEnumerator や IEnumerable の2つを実装しようとして、はまってしまいました。
それぞれに同じ名前の GetCurrent メソッドがあるためのようです。
そこで、とりあえずインターフェースを継承しないことにしました。
というのは docwiki に
という記述があり、必ずしもインターフェースを実装しなくても良いからです。
この場合、コンパイラはメソッドの名前から判断しているようです。
つまり、クラスを for in do に対応させるためには
- GetEnumerator メソッドの実装
- GetCurrent メソッドの実装
- MoveNext メソッドの実装
- Current プロパティの実装
このうち、1は実際に for in に渡されるクラスで実装します。
2以降は、通常別のクラスに記載します。
TStyleProvider で言うと1は
TStyleProvider = class(TObject)(省略)publicfunction GetEnumerator: TStyleEnumerator;end;(省略)function TStyleProvider.GetEnumerator: TStyleEnumerator;beginResult := TStyleEnumerator.Create(Self);end;
のように TStyleProvider で実装しています。
ここで返している TStyleEnumerator が2以降を実装しています。
TStyleEnumerator の宣言と実装は以下のようになっています。
TStyleEnumerator = class(TObject)privateFProvider: TStyleProvider;FIndex: Integer;publicconstructor Create(const iProvider: TStyleProvider);function GetCurrent: String;function MoveNext: Boolean;procedure Reset;property Current: String read GetCurrent;end;implementation{ TStyleProvider.TStyleEnumerator }constructor TStyleProvider.TStyleEnumerator.Create(const iProvider: TStyleProvider);begininherited Create;FProvider := iProvider;FIndex := -1;end;function TStyleProvider.TStyleEnumerator.GetCurrent: String;beginResult := FProvider[FIndex];end;function TStyleProvider.TStyleEnumerator.MoveNext: Boolean;beginInc(FIndex);Result := (FIndex < FProvider.Count);end;procedure TStyleProvider.TStyleEnumerator.Reset;beginFIndex := -1;end;
つまり、MoveNext でインデックスを1つ進め、GetCurrent で、そのインデックスが示すデータを返す、というだけです。
ただ注意しないといけないのは、インデックスの初期値は -1 にしておくということです。
それは、
- Create
- MoveNext
- GetCurrent
つまり最初の要素の取得だとしても MoveNext が呼ばれてから GetCurrent が呼ばれるので、インデックスの初期値を -1 にしておくと、都合が良い、ということです。
また、もう一つ疑問点があります。
GetEnumerator で渡す TStypleEnumerator は TInterfacedObject などにして解放する必要があるのではないか?ということです。
しかし、TStringList の IEnumerator の実装である System.Classes.TStringsEnumerator のコードを見てみると、特に解放している様子はありませんでした。
コンパイラが自動的にやっているようです。
TStringsEnumerator のようにデフォルトで用意されている Enumerator もありますし、自前で Enumerator を実装するのも簡単なので、これから作成するクラスでは対応すると良いかも知れません。
0 件のコメント:
コメントを投稿