using System;
using System.Linq.Expressions;
static class Program
{
// Expression (式) による Add メソッドの生成
static Func<int, int, int> AddByExpression()
{
// 生成したい式
// (int x, int y) => x + y
var x = Expression.Parameter(type: typeof(int)); // 引数 x の式
var y = Expression.Parameter(type: typeof(int)); // 引数 y の式
var add = Expression.Add (left: x, right: y); // x + y の式
var lambda = Expression.Lambda (add, x, y ); // (x, y) => x + y の式
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<int, int, int>)lambda.Compile();
}
static void Main()
{
var addByExpression = AddByExpression(); // デリゲートを動的に生成
var answerByExpression = addByExpression(1, 2); // 生成したデリゲートの呼び出し
Console.WriteLine("answerByExpression: {0}", answerByExpression);
}
}
using System.Text;
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
// 文字列へ変換するメソッド ToString() をオーバーライド
public override string ToString()
{
return new StringBuilder()
.Append("Title: ").Append(Title)
.Append(", " )
.Append("Price: ").Append(Price)
.ToString();
// または:
//return string.Format("Title: {0}, Price: {1}", Title, Price);
}
}
XML や HTML への変換
次は、XML や HTML に変換するメソッドだ。
XML への変換の拡張メソッドと HTML の table への変換の拡張メソッドを、それぞれ作成してみる。
using System.Collections.Generic;
using System.Text;
public static class BookExtensions
{
// XML へ変換
public static string ToXml(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<?xml version=\"1.0\"?>");
@this.ForEach(
book =>
stringBuilder.Append("<Book>" )
.Append("<Title>").Append(book.Title).Append("</Title>")
.Append("<Price>").Append(book.Price).Append("</Price>")
.Append("</Book>")
);
return stringBuilder.ToString();
}
// HTML の table へ変換
public static string ToHtmlTable(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody>");
@this.ForEach(
book => stringBuilder.Append("<tr><td>").Append(book.Title).Append("</td>" )
.Append( "<td>").Append(book.Price).Append("</td></tr>"));
return stringBuilder.Append("</tbody></table>").ToString();
}
}
上のコードで呼び出している ForEach メソッドは別クラスに拡張メソッドとして用意した。
using System;
using System.Collections.Generic;
// ForEach 用
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var item in @this)
action(item);
}
}
文字列を生成する静的な例のテスト
それでは、これらのメソッドを呼び出してみよう。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToString());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXml());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTable());
}
}
こちらも呼び出してみよう。先の Program クラスを少し書き換えてリフレクション版のメソッドを呼ぶようにしてみる。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByReflection());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXmlByReflection());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTableByReflection());
}
}
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByEmitExtensions初期型
{
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByEmit.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit初期型());
}
}
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;
}
}
Reflection.Emit によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッド キャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByEmitExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByEmit.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit改());
}
}
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByExpressionExtensions初期型
{
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByExpression.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
今回も、次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression初期型());
}
}
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;
}
}
式木によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByExpressionExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByExpression.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression改());
}
}
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())
// 改良前 (メソッドのキャッシュ無し)
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改());
}
}