DelphiでCSVファイルを作成するクラス

ビフォアーアフター(CSV出力機能編) – TIM Labs」を読んで、CSVファイルの作成処理を見直すことにしました。

問題

該当する出力機能のソースコードを発見したあなたは何とも言えない気持ちになりました。
具体的には以下のようなソースコードになっていたからです (※掲載にあたり比較的読み易くなるよう整理してあります):

ビフォアーアフター(CSV出力機能編) – TIM Labs」より

ここで紹介されているようなソースコードが、私のプログラムにもあります。

CSVファイルの作成処理を見直すことにしました。

■願望

以下のような形で記述できるようにします。

var
  FieldList: TList<TPair<String, String>>; //列名とプロパティ名のペア
  Staffs: TObjectList<TStaff>; //保存する社員データ
begin
  //社員一覧
  Staffs := GatStaffs();

  //CSVファイルに出力する列名とプロパティ名
  FieldList := TList<TPair<String, String>>.Create;
  FieldList.Add(TPair<String, String>.Create('社員番号', 'Id'));
  FieldList.Add(TPair<String, String>.Create('名前', 'Name'));
  FieldList.Add(TPair<String, String>.Create('メールアドレス', 'Email'));

  //CSVファイルに保存する
  TCsv<TStaff>.ExportFile('C:\\Staffs.csv', FieldList, Staffs);

CSVファイルに出力する列名とプロパティ名のペアはもっと簡単にかけそうな気がしますが、方法がわかりませんでした。

■実装

まずは社員クラス。

ID,Name,Emailのプロパティを持ちます。

TStaff = class
private
  FId: String;
  FName: String;
  FEmail: String;
public
  constructor Create(Id: String; Name: String; Email: String);
  property Id:String read FId;
  property Name:String read FName;
  property Email:String read FEmail;
end;

次に社員のリストを返す関数。

function GatStaffs(): TObjectList<TStaff>;
begin
  Result := TObjectList<TStaff>.Create();
  Result.Add(TStaff.Create('1', '鈴木', 'suzuki@example.com'));
  Result.Add(TStaff.Create('2', '田中', 'tanaka@example.com'));
  Result.Add(TStaff.Create('3', '木村', 'kimura@example.com'));
end;

さて、本題のCSVファイルに保存する処理です。

総称型を使う関数は、グローバル関数では作成できません。
TCsvクラスのクラス関数として定義しました。

TCsv<T: class> = class
private
  //オブジェクトのプロパティの値を取得する
  class function GetFieldValue(Obj: T; PropName: String): String;
public
  //CSVファイルに出力する
  class procedure ExportFile(
    FileName: String;  //出力するファイルのファイル名
    FieldList: TList<TPair<String, String>>; //出力する列名と値
    List: TList<T>); //出力データのリスト
end;

プロパティ名からオブジェクトのプロパティの値を取得する関数です。

総称型Tからプロパティの値を取得できなかったため、一度TObject型に変換しています。

// @param Obj オブジェクト
// @param PropName プロパティ名
// @return オブジェクトのプロパティの値
class function TCsv<T>.GetFieldValue(Obj: T;
  PropName: String): String;
var
  Context: TRttiContext;
  RttiType: TRttiType;
  RttiProperty: TRttiProperty;
  C: TObject;
begin
  if not (Obj is TObject) then Exit;

  C := Obj;
  Context := TRttiContext.Create;
  RttiType := Context.GetType(C.ClassType);
  RttiProperty := RttiType.GetProperty(PropName);
  Result := RttiProperty.GetValue(C).AsString;
end;

CSVファイルに出力する関数です。

ファイルの保存にはTStringList.SaveToFileを使っています。
CSVファイルのサイズが大きい時は、もう一工夫が必要でしょう。

class procedure TCsv<T>.ExportFile(FileName: String;
  FieldList: TList<TPair<String, String>>; List: TList<T>);
var
  Csv: TStringList;
  Row: TStringList;
  Field: TPair<String, String>;
  Obj: T;
begin
  Csv := TStringList.Create;
  Row := TStringList.Create;

  //見出し
  for Field in FieldList do
  begin
    Row.Add(Field.Key);
  end;
  Csv.Add(Row.CommaText);

  //値
  for Obj in List do
  begin
    Row.Clear;
    for Field in FieldList do
    begin
      Row.Add(GetFieldValue(Obj, Field.Value));
    end;
    Csv.Add(Row.CommaText);
  end;
  Csv.SaveToFile(FileName);
  Row.Destroy;
  Csv.Destroy;
end;

オブジェクトからプロパティの値を取得するのに、今回はRTTIを使用しました。
他に、無名メソッドを使う方法も考えられます。
無名メソッドの方が柔軟性が高いかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください