かなり前に「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.Out は IRecordable を実装していないので,コンパイルできません。こんなときに役立つのがこのライブラリーで,以下のように書くことができます。
[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 や類似する形式のファイルを扱う機会があったら,ぜひこれらのライブラリーをご使用ください。
脚注
- 実際は CSV 以外のテキスト形式も扱えます。 [↩]