「ビフォアーアフター(CSV出力機能編) – TIM Labs」を読んで、CSVファイルの作成処理を見直すことにしました。
問題
該当する出力機能のソースコードを発見したあなたは何とも言えない気持ちになりました。
具体的には以下のようなソースコードになっていたからです (※掲載にあたり比較的読み易くなるよう整理してあります):
ここで紹介されているようなソースコードが、私のプログラムにもあります。
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を使用しました。
他に、無名メソッドを使う方法も考えられます。
無名メソッドの方が柔軟性が高いかもしれません。