[C#][.NET][Roslyn] メタプログラミング入門 – 応用編 – オブジェクトの文字列変換のメタプログラミング (Roslyn 編)

※ 「[C#][.NET][式木] メタプログラミング入門 – 応用編 – オブジェクトの文字列変換のメタプログラミング (式木編)」の続き。
Roslyn によるメタプログラミング
Roslyn によるメタプログラミングに関しては、以前、次にあげる記事で扱った。参考にしてほしい。
- [C#][.NET][Roslyn] メタプログラミング入門 – Roslyn による Add メソッドの動的生成
- [C#][.NET] メタプログラミング入門 – メソッド呼び出しのパフォーマンスの比較
今回も、題材は同じく 「[C#][.NET] メタプログラミング入門 – 応用編 – オブジェクトの文字列変換を静的/動的に行う」の中の「(デバッグ用の) 文字列に変換」だ。
例えば、次のようなクラスのオブジェクトを文字列に変換する。
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
}
Roslyn に渡す C# のソースコード
前回は、式木でラムダ式を組み立て、それをコンパイルすることにより、デリゲートを生成した。
Book クラスの場合を例にあげ、プログラムによって生成したい「文字列変換を行うラムダ式」として次のものを想定したのだった。
// 動的に作りたいラムダ式の例 (実際のコードは targetType による):
item => new StringBuilder().Append("Title: ").Append(item.Title)
.Append(", ")
.Append("Price: ").Append(item.Price)
.ToString()
対象とするオブジェクトのクラスによってラムダ式が異なるため、式木は動的に生成した。
今回は、「文字列変換を行うラムダ式」の C# のソースコードを動的に作成し、そのソースコードから Roslyn を用いてデリゲートを生成することにしよう。
先ず、Roslyn を使う前に、上のようなラムダ式の C# のソースコードを、リフレクションを用いて動的に作成する。
対象とするオブジェクトとその型から、C# のソースコードを文字列として作成するメソッドを書く。
この時、リフレクションとジェネリックを用いて、型に依存しないようにする。
// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
// ToString() メソッドの C# のソースコードを作成する
public static string CreateCodeOfToStringByRoslyn<T>()
{
// 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
// item => new StringBuilder().Append("Title: ").Append(item.Title)
// .Append(", ")
// .Append("Price: ").Append(item.Price)
// .ToString()
var bodyCode = "new StringBuilder()" +
string.Join(".Append(\", \")",
typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where (property => property.CanRead)
.Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
".ToString()";
return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
}
}
これで正しい「文字列変換を行うラムダ式」の C# のソースコードが文字列として作成されるか、Book クラスの場合で試してみよう。
using System;
static class Program
{
static void Main()
{
var code = ToStringGeneratorByRoslyn.CreateCodeOfToStringByRoslyn<Book>();
Console.WriteLine(code);
}
}
実行してみよう。
(Func<Book, string>)(item => new StringBuilder().Append("Title: ").Append(item.Title).Append(", ").Append("Price: ").Append(item.
Price).ToString())
目的とするソースコードができているようだ。ここまでは、まだ Roslyn は使用していない。
Roslyn によるオブジェクトの文字列への変換プログラム生成プログラム
この C# のソースコードから Roslyn を使ってデリゲートを生成しよう。このやり方は、「Roslyn による Add メソッドの動的生成」や「メソッド呼び出しのパフォーマンスの比較」で行ったのと同様だ。
次のようになる。
using Roslyn.Scripting.CSharp;
using System;
using System.Linq;
using System.Reflection;
// ToString メソッド生成器 (Roslyn 版)
public static class ToStringGeneratorByRoslyn
{
// メソッドを生成
public static Func<T, string> Generate<T>()
{
var code = CreateCodeOfToStringByRoslyn<T>(); // C# のソースコードを生成
return Generate<T>(code: code);
}
// Roslyn でメソッドを生成
static Func<T, string> Generate<T>(string code)
{
var engine = CreateEngine(); // スクリプトエンジン
var session = engine.CreateSession(); // 実行するには Session が必要
return (Func<T, string>)session.Execute(code: code); // コードの生成
}
// Roslyn のスクリプトエンジンを作成する
static ScriptEngine CreateEngine()
{
var engine = new ScriptEngine(); // Roslyn のスクリプトエンジン
engine.ImportNamespace(@namespace: "System" ); // System 名前空間を using
engine.ImportNamespace(@namespace: "System.Text"); // System.Text 名前空間を using
engine.AddReference(typeof(ToStringGeneratorByRoslyn).Assembly); // このアセンブリ内のクラスを使用する為に参照
return engine;
}
// ToString() メソッドの C# のソースコードを作成する
static string CreateCodeOfToStringByRoslyn<T>()
{
// 動的に作りたい C# のソースコードの例 (実際のコードは typeof(T) による):
// item => new StringBuilder().Append("Title: ").Append(item.Title)
// .Append(", ")
// .Append("Price: ").Append(item.Price)
// .ToString()
var bodyCode = "new StringBuilder()" +
string.Join(".Append(\", \")",
typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(property => property.CanRead)
.Select(property => string.Format(".Append(\"{0}: \").Append(item.{0})", property.Name))) +
".ToString()";
return string.Format("(Func<{0}, string>)(item => {1})", typeof(T).FullName, bodyCode);
}
}
Roslyn によるオブジェクトの文字列への変換 (キャッシュ無し)
これを使って、これまでと同様、先ずはキャッシュ無しの変換メソッドを作ろう。
次のプログラムでは、呼ばれる度に毎回コードを生成する。
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByRoslynExtensions初期型
{
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn初期型<T>(this T @this)
{
return ToStringGeneratorByRoslyn.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn初期型());
}
}
実行結果は次のようになり、正しく動作する。
Title: Metaprogramming C#, Price: 3200
生成したメソッドのキャッシュ
では、キャッシュを利用してみよう。
今回もメソッド キャッシュ クラスを使う。
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
Roslyn によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByRoslynExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByRoslyn.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn改());
}
}
やはり、実行結果は同じだ。
Title: Metaprogramming C#, Price: 3200
まとめ
今回は、前回の式木を用いた方法に続き、Roslyn を使って動的に「オブジェクトを文字列に変換する」メソッドを生成するプログラムを作成した。
次回は、メタプログラミングによる文字列変換のまとめとして、それぞれの方法でのパフォーマンスの比較を行う。
関連
関連記事
[C#][.NET][Roslyn] Build 2014 でオープンソースになったと発表された Roslyn のソースコードを弄ってみた
Build 2014 での Roslyn 関連の発表 先日、マイクロソフトの開発 ...
[TypeScript][C#][Windows ストア アプリ] ImageData によるマンデルブロ集合の描画
※ 『 ImageData によるピクセル単位の描画』の続き。 前回は、HTML ...
[C#][.NET][Roslyn] メタプログラミング入門 – Roslyn による Add メソッドの動的生成
※ 「 メタプログラミング入門 - 式木による Add メソッドの動的生成」の続 ...
[C#][.NET][Roslyn][式木] Room metro #23 大阪「メタプログラミング C#」の資料公開
Room metro #23 大阪 (3月1日) でやったセッションの資料を公開 ...
[Event] 「Hokuriku ComCamp 2016 powered by MVPs」 (2016年2月20日)が開催されます
この度、石川県金沢市で、毎年恒例のマイクロソフトの技術に関する勉強会 ComCa ...
ディスカッション
コメント一覧
まだ、コメントがありません