CSV レコードを任意のクラスにマッピングする


かなり前に「CSV レコードをオブジェクトにマッピングする」というタイトルで CSV の話を書きました。実はこのときのものを RecycleBin.TextTables というライブラリーの形で NuGet で公開しています。

そしてさらに前に「インターフェイスを実装したふりをさせる」というタイトルで動的にプロキシークラスを作るという話を書きました。これもまた RecycleBin.DynamicProxy というライブラリーの形で NuGet で公開しています。

実はこれらを組合せると超強力だということに気が付いたので,ここにまとめます。

RecycleBin.TextTables

まず RecycleBin.TextTables ですが, CSV[A]を読み込む際に,以下のような感じで各レコードを任意のクラスにマッピングするというものです。

public class User
{
   [Column("id")]
   public string ID { get; set; }
   [Column("name")]
   public string NickName { get; set; }
   [Column("join")]
   public DateTime JoinDate { get; set; }
}

using (var reader = new CsvReader("users.csv"))
{
   var users = from user in reader.ReadToEnd<User>()
               ...
}

類似したライブラリーに LINQtoCSV というライブラリーがありますが,こちらの方がパフォーマンスも良いし機能も豊富です。

RecycleBin.DynamicProxy

RecycleBin.DynamicProxy はサードパーティー製のクラスが特定のインターフェイスを実装してるとうれしいと思った時に,実装してもらうためのライブラリーです。

たとえば以下のようなサードーパーティー製のロギングライブラリーがあったとします。

public interface IRecordable
{
   void AppendText(string text);
}

public static class Logger
{
   public static void LogMessage(string message, IRecordable recordable)
   {
      recordable.AppendText(message);
   }
}

そしてコンソールにログ出力をしたいと考えたとします。すると次のように書きたくなります。

Logger.LogMessage("FOO", Console.Out)

ところが Console.OutIRecordable を実装していないので,コンパイルできません。こんなときに役立つのがこのライブラリーで,以下のように書くことができます。

[ProxyInterface(typeof(IRecordable))]
public interface IRecordableProxy
{
   [ProxyMethod(Target = "WriteLine", EntityType = typeof(TextWriter))]
   void AppendText(string text);
}

var builder = new DynamicProxyBuilder("LoggingProxy");
var proxy = builder.CreateProxy(typeof(IRecordableProxy), Console.Out, typeof(TextWriter)) as IRecordable;
Logger.LogMessage("FOO", proxy);

やっていることは実行時にラッパークラスを作っているだけですが,これが意外と便利です。

組み合わせる

これら 2 つのライブラリーを組み合わせると, CSV レコードを自分が定義したクラスのみでなく,任意のクラスにマッピングできます。

たとえば先述の例の User クラスがユーザー定義でないために Column 属性を付けられないとします。そのときは次のようなプロキシーを作成します。

public interface IUserProxy
{
   [Column("id")]
   [ProxyMethod]
   public string ID { get; set; }
   [Column("name")]
   [ProxyMethod]
   public string NickName { get; set; }
   [Column("join")]
   [ProxyMethod]
   public DateTime JoinDate { get; set; }
}

そして以下のような拡張メソッドを定義します。

public static class TextTableReaderExtensions
{
   private static readonly DynamicProxyBuilder builder = new DynamicProxyBuilder("ProxyLibrary");

   public static IEnumerable<T> ReadToEnd<T, TProxy>(this TextTableReader reader)
   {
      var proxyType = typeof(TProxy);
      while (reader.MoveNext())
      {
         var prototype = Activator.CreateInstance<T>();
         var proxy = builder.CreateProxy(proxyType, prototype);
         reader.Current.Convert(proxyType, ref proxy);
         yield return prototype;
      }
   }
}

すると先述の例と同じようなことが次のようにできます。

using (var reader = new CsvReader("users.csv"))
{
   var users = from user in reader.ReadToEnd<User, IUserProxy>()
               ...
}

まとめ

  • RecycleBin.TextTables は CSV などのテキスト形式のレコードをクラスにマッピングできる。
  • RecycleBin.DynamicProxy は自分が編集できないクラスに対するメタ操作を提供する。
  • これらを組み合わせると,テキスト形式のレコードを,自分が編集できないクラスであっても,マッピングできる。

いずれも自分で作ったライブラリーなのに,この強力なコンビネーションに気付かなかったなんて不覚です。ということで, CSV や類似する形式のファイルを扱う機会があったら,ぜひこれらのライブラリーをご使用ください。

脚注

  1. 実際は CSV 以外のテキスト形式も扱えます。 []