for in do の活用 (Delphi 2005 以降)

 Delphi 2005 以降では、他言語で言う所の foreach に相当する for in do が使えます。for in do が使えるようになって結構経ちますし、Unicode 版 Delphi ではもれなく使えます。for in do 対応のクラスも増えてきましたので、積極的に使ってみましょう。

for in do の構文

 for in do の構文は以下のようになっています。

for Element in ArrayExpr do Stmt;
for Element in StringExpr do Stmt;
for Element in SetExpr do Stmt;
for Element in CollectionExpr do Stmt;
for Element in Record do Stmt;

 コンテナには

 が利用可能です。また、RTL の以下のクラスと下位クラスでは for in do 構文が利用可能です。 詳しくは Docwiki の "4.5.4 for 文を使用するコンテナの繰り返し" を参照してください。

配列を回す

 簡単な所で、配列を回してみます。

var
  StrArr: array of string;
  i: Integer;
begin
  for i:=Low(StrArr) to High(StrArr) do
    Memo1.Lines.Add(StrArr[i]);
end;    

 文字列の動的配列は for to do を使ってこのように回していました。for in do なら?

var
  StrArr: array of string;
  s: string;
begin
  for s in StrArr do
    Memo1.Lines.Add(s);
end;    

 このようになります。数値配列なら

var
  IntArr: array of Integer;
  i: Integer;
begin
  for i in IntArr do
    Memo1.Lines.Add(IntToStr(i));
end

 こんな感じです。

文字を回す

 今度は文字を回してみます。

var
  s: string;
  i: Integer;
begin
  s := 'ABCDEFG';
  for i:=1 to Length(s) do
    Memo1.Lines.Add(s[i]);
end;    

 for in do なら?

var
  s: string;
  c: char;
begin
  s := 'ABCDEFG';
  for c in s do
    Memo1.Lines.Add(c);
end;    

 このようになります。

Set 式を回す

 同様に Set 式を回してみます。

var
  i: Integer;
begin
  for i in [0..579do
    Memo1.Lines.Add(IntToStr(i));
end;    

 ...という事は?

var
  fs: TFontStyle;
begin
  for fs in Font.Style do
    if fs = fsBold then
      Memo1.Lines.Add('Include Bold');
end;

 イロイロ回せますね。但し、

var
  i: Integer;
begin
  for i in [100200100do
    Memo1.Lines.Add(IntToStr(i));
end

 これの結果は "100 200 100" ではなく "100 200" となる事に注意。

ジェネリクス配列を使って文字列を回す (Delphi 2010 以降)

 ちょっとキモいコードですが、

var
  s: string;
begin
  for s in TArray<string>.Create('AQUACURE','SAFSPRIN''ADRAVIL'do
    Memo1.Lines.Add(s);
end;   

 知っておくと便利です (元ネタ: for-in-doにもっと頑張ってもらう。(全力わはー))。

SplitString を使って文字列を回す (Delphi XE 以降)

 ジェネリクス(文字)配列を回す方法と同じ用途で使うのであれば、SplitString() で代用できます。

var
  s: string;
begin
  for s in SplitString('AQUACURE,SAFSPRIN,ADRAVIL'','do
    Memo1.Lines.Add(s);
end;

 なお、SplitString() は XE から追加されていますが、元ネタになった SplitString() は XMLDoc.pas の中にローカル関数として Delphi 7 から存在します。

正規表現のマッチ結果を回す (Delphi XE 以降)

 Delphi XE には正規表現クラスとして、TRegEx が実装されています。この TRegEx のマッチ結果を回してみましょう。

uses
  ..., RegularExpressions;

var
  Match: TMatch;
begin
  for Match in TRegEx.Matches('ABCABCABC''C'do
    Memo1.Lines.Add(Format('%s (%d, %d)', [Match.Value, Match.Index, Match.Length]));
end;

 短いですねー。

正規表現のマッチ結果を回す (Delphi 2005 以降)

 SkRegExp は Delphi 2005 以降で使える正規表現クラスです。SkRegularExpressions.pas を併用する事により Delphi 2005 等でもマッチ結果を for in do で回す事ができます。

uses
  ..., SkRegularExpressions;

var
  Match: TMatch;
begin
  for Match in TRegEx.Matches('ABCABCABC''C'do
    Memo1.Lines.Add(Format('%s (%d, %d)', [Match.Value, Match.Index, Match.Length]));
end;

 純正の TRegEx を簡単にリプレスできますし、その逆も簡単です。

ファイル検索結果を回す (Delphi 2010 以降)

 特定のフォルダにあるファイルを再帰で取得して列挙するには FindFirst() / FindNext() / FindClose() を使うのが一般的です。

procedure TForm1.EnumFiles(const aDir, aFileMask: String);
  procedure _EnumFiles(const aDir, aFileMask: String);
  var
    sr: TSearchRec;
  begin
    if FindFirst(aDir + '*.*', faAnyFile, sr) = 0 then
      begin
        repeat
          if (sr.Attr and faDirectory) = faDirectory then
            begin
              if StringReplace(sr.Name, '.''' ,[rfReplaceAll]) <> '' then
                _EnumFiles(aDir + sr.Name + PathDelim, aFileMask);
            end
          else
            begin
              if Masks.MatchesMask(sr.Name, aFileMask) then
                Memo1.Lines.Add(aDir + sr.Name);
            end;
        until FindNext(sr) <> 0;
        FindClose(sr);
      end;
  end;
begin
  _EnumFiles(IncludeTrailingPathDelimiter(aDir), aFileMask);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  EnumFiles('C:\TEST''*.pas');
end;

 こんな感じになるかと思います。これを、Delphi 2010 から追加された TDirectory でやってみましょう。

uses
  ..., IOUtils;

procedure TForm1.Button1Click(Sender: TObject);
var
  FileName: String;
begin
  for FileName in TDirectory.GetFiles('C:\TEST''*.pas', TSearchOption.soAllDirectories) do
    Memo1.Lines.Add(FileName);
end;

 めちゃめちゃスッキリ書けます。

for in do に対応したクラスを作るには?

 詳細は Docwiki の "4.5.4 for 文を使用するコンテナの繰り返し" を参照してください。

 for in do は動的配列にも使えるのですから、小規模な列挙であれば戻り値として動的配列を返す関数やクラスメソッドを実装すればいい事になります。例として文字列の動的配列である TStringDynArray (array of string) を返す関数を作ってみます。

uses
  ..., Types;

function TForm1.EnumTest: TStringDynArray;
begin
  SetLength(result, 3);
  result[0] := 'ABC';
  result[1] := 'DEF';
  result[2] := 'GHI';
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string;
begin
  for s in EnumTest do
    Memo1.Lines.Add(s);
end;

 大規模な...または大規模になる可能性のある集合を列挙する場合にはちゃんと GetEnumerator() を実装して下さい。


 BACK