2012年10月24日水曜日

クラスを for in do に対応させる

TStyleProvider を作成する過程で、やったことの無かった for in do に対応させてみようとコードを組んでみました。

IEnumeratorIEnumerable の2つを実装しようとして、はまってしまいました。
それぞれに同じ名前の GetCurrent メソッドがあるためのようです。

そこで、とりあえずインターフェースを継承しないことにしました。
というのは docwiki に


という記述があり、必ずしもインターフェースを実装しなくても良いからです。
この場合、コンパイラはメソッドの名前から判断しているようです。

つまり、クラスを for in do に対応させるためには
  1. GetEnumerator メソッドの実装
  2. GetCurrent メソッドの実装
  3. MoveNext メソッドの実装
  4. Current プロパティの実装
という4つの実装が必要で、場合によってはさらに Reset メソッドの実装も必要です。

このうち、1は実際に for in に渡されるクラスで実装します。
2以降は、通常別のクラスに記載します。

TStyleProvider で言うと1は

TStyleProvider = class(TObject)
(省略)
public
function GetEnumerator: TStyleEnumerator;
end;
(省略)
function TStyleProvider.GetEnumerator: TStyleEnumerator;
begin
Result := TStyleEnumerator.Create(Self);
end;

のように TStyleProvider で実装しています。
ここで返している TStyleEnumerator が2以降を実装しています。

TStyleEnumerator の宣言と実装は以下のようになっています。

TStyleEnumerator = class(TObject)
private
FProvider: TStyleProvider;
FIndex: Integer;
public
constructor 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);
begin
inherited Create;
FProvider := iProvider;
FIndex := -1;
end;
function TStyleProvider.TStyleEnumerator.GetCurrent: String;
begin
Result := FProvider[FIndex];
end;
function TStyleProvider.TStyleEnumerator.MoveNext: Boolean;
begin
Inc(FIndex);
Result := (FIndex < FProvider.Count);
end;
procedure TStyleProvider.TStyleEnumerator.Reset;
begin
FIndex := -1;
end;

つまり、MoveNext でインデックスを1つ進め、GetCurrent で、そのインデックスが示すデータを返す、というだけです。
ただ注意しないといけないのは、インデックスの初期値は -1 にしておくということです。
それは、
  1. Create
  2. MoveNext
  3. GetCurrent
という順番でメソッドが呼ばれるからです。

つまり最初の要素の取得だとしても MoveNext が呼ばれてから GetCurrent が呼ばれるので、インデックスの初期値を -1 にしておくと、都合が良い、ということです。

また、もう一つ疑問点があります。
GetEnumerator で渡す TStypleEnumerator は TInterfacedObject などにして解放する必要があるのではないか?ということです。

しかし、TStringList の IEnumerator の実装である System.Classes.TStringsEnumerator のコードを見てみると、特に解放している様子はありませんでした。
コンパイラが自動的にやっているようです。

TStringsEnumerator のようにデフォルトで用意されている Enumerator もありますし、自前で Enumerator を実装するのも簡単なので、これから作成するクラスでは対応すると良いかも知れません。

0 件のコメント:

コメントを投稿