[C#][.NET][CodeDOM] メタプログラミング入門 – CodeDOM によるクラスの生成
※ 「[C#][.NET][CodeDOM] メタプログラミング入門 – CodeDOM による Hello world!」の続き。
CodeDOM によるクラスの動的生成
前回は、CodeDOM を使って Hello world! を表示するプログラムを動的に生成した。
もう少し CodeDOM を使ってみよう。
今回は、CodeDOM を使ってクラスを作ってみる。
次のような Item クラスを動的に作ることにする。
namespace CodeDomClassDemo { public class Item { private int price; public int Price { get { return this.price; } set { this.price = value; } } public override string ToString() { return (this.Price + "円"); } } }
次のような手順だ。
- CodeDomClassDemo 名前空間の生成
― CodeNamespace を new - Item クラスの生成
― CodeTypeDeclaration を new - Item クラスを CodeDomClassDemo 名前空間へ追加
- price フィールドを生成
― CodeMemberField を new - price フィールドを Item クラスに追加
- Price プロパティを生成
― CodeMemberProperty を new - Price プロパティの Get を追加
― price フィールドへの参照を return する文を生成して追加 - Price プロパティの Set を追加
― price フィールドへの参照に value を代入する文を生成して追加 - Price プロパティを Item クラスに追加
- ToString メソッドを生成
― CodeMemberMethod を new - ToString メソッドの中身を追加
― Price プロパティへの参照に &qout;円&qout; を + して return する文を生成して追加 - ToString メソッドを Item クラスに追加
では、やってみよう。
前回同様、System.CodeDom 名前空間を使用する。
using System.CodeDom; class Program { // Item クラスの Code DOM static CodeNamespace ItemClassCodeDom() { // CodeDomHelloWorldDemo 名前空間 var nameSpace = new CodeNamespace(name: "CodeDomClassDemo"); //// System 名前空間のインポート //nameSpace.Imports.Add(new CodeNamespaceImport(nameSpace: "System")); // Item クラス var itemClass = new CodeTypeDeclaration(name: "Item"); // CodeDomHelloWorldDemo 名前空間に Item クラスを追加 nameSpace.Types.Add(itemClass); // price フィールド var priceField = new CodeMemberField(type: typeof(int), name: "price") { Attributes = MemberAttributes.Private // private }; // Item クラスに price フィールドを追加 itemClass.Members.Add(priceField); // Price プロパティ var priceProperty = new CodeMemberProperty { Name = "Price" , // 名前は、Price Attributes = MemberAttributes.Public | MemberAttributes.Final, // public で virtual じゃない Type = new CodeTypeReference(typeof(int)) // 型は int }; // price フィールドへの参照 var priceFieldReference = new CodeFieldReferenceExpression( targetObject: new CodeThisReferenceExpression(), // this. fieldName : "price" // price ); // Price プロパティの Get を追加 priceProperty.GetStatements.Add( new CodeMethodReturnStatement(priceFieldReference) // price フィールドへの参照を return する文 ); // Price プロパティの Set を追加 priceProperty.SetStatements.Add( new CodeAssignStatement( // 代入文 left : priceFieldReference , // 左辺は、price フィールドへの参照 right: new CodePropertySetValueReferenceExpression() // 右辺は、value ) ); // Item クラスに Price プロパティを追加 itemClass.Members.Add(priceProperty); // ToString メソッド var toStringMethod = new CodeMemberMethod { Name = "ToString" , // 名前は、ToString Attributes = MemberAttributes.Public | MemberAttributes.Override, // public で override ReturnType = new CodeTypeReference(typeof(string)) // 戻り値の型は、string }; // Price プロパティへの参照 var pricePropertyReference = new CodePropertyReferenceExpression( targetObject: new CodeThisReferenceExpression(), // this. propertyName: "Price" // Price ); // ToString メソッドの中身 toStringMethod.Statements.Add( new CodeMethodReturnStatement( // return 文 new CodeBinaryOperatorExpression( // 二項演算子の式 left : pricePropertyReference , // Price プロパティへの参照 op : CodeBinaryOperatorType.Add , // + right: new CodePrimitiveExpression("円") // "円" ) ) ); // Item クラスに ToString メソッドを追加 itemClass.Members.Add(toStringMethod); return nameSpace; } static void Main() { // Item クラスを含む名前空間を CodeDOM で生成 var itemClassCodeDom = ItemClassCodeDom(); } }
この ItemClassCodeDom メソッドで、Item クラスを含む名前空間を生成したことになる。
CodeDOM によるソースコードの生成
前回同様、上で作った名前空間から CodeDOM を使って C# のソースコードを生成してみる。
手順は前回と全く同じで、System.CodeDom.Compiler 名前空間を使用する。
using System; using System.CodeDom; using System.CodeDom.Compiler; using System.IO; using System.Text; class Program { // Item クラスの Code DOM static CodeNamespace ItemClassCodeDom() { …… 同じなので省略 …… } // 名前空間からソースコードを生成 static string GenerateCode(CodeNamespace codeNamespace) { // コンパイル オプション var compilerOptions = new CodeGeneratorOptions { IndentString = " ", BracingStyle = "C" }; var codeText = new StringBuilder(); using (var codeWriter = new StringWriter(codeText)) { // 名前空間からソースコードを生成 CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(codeNamespace, codeWriter, compilerOptions); } return codeText.ToString(); // 生成されたソースコード } static void Main() { // Item クラスを含む名前空間を CodeDOM で生成 var itemClassCodeDom = ItemClassCodeDom(); // Item クラスのソースコードを生成 var code = GenerateCode(itemClassCodeDom); Console.WriteLine(code); } }
実行してみると、次のように C# のソースコードが表示される。
namespace CodeDomClassDemo { public class Item { private int price; public int Price { get { return this.price; } set { this.price = value; } } public override string ToString() { return (this.Price + "円"); } } }
GenerateCode メソッド中の "C#" の部分を "VB" に置き換えて実行した場合は次の通り。
Namespace CodeDomClassDemo Public Class Item Private price As Integer Public Property Price() As Integer Get Return Me.price End Get Set Me.price = value End Set End Property Public Overrides Function ToString() As String Return (Me.Price + "円") End Function End Class End Namespace
CodeDOM によるアセンブリの生成
次に、CodeDOM で生成した名前空間をコンパイルしてアセンブリを生成してみよう。
前回は、アセンブリをファイルに出力したが、今回はオンメモリで生成してみる。
生成したアセンブリの中から Item クラスを取り出してインスタンスを生成し、Price プロパティや ToString メソッドを使ってみよう。
using System; using System.CodeDom; using System.CodeDom.Compiler; using System.IO; using System.Linq; using System.Reflection; using System.Text; class Program { // Item クラスの Code DOM static CodeNamespace ItemClassCodeDom() { …… 同じなので省略 …… } // 名前空間からソースコードを生成 static string GenerateCode(CodeNamespace codeNamespace) { …… 同じなので省略 …… } // 名前空間をアセンブリへコンパイル static Assembly CompileAssembly(CodeNamespace codeNamespace) { var codeCompileUnit = new CodeCompileUnit(); // コンパイル単位 codeCompileUnit.Namespaces.Add(codeNamespace); // コンパイル単位に名前空間を追加 // アセンブリへコンパイル var compilerResults = CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom( options : new CompilerParameters { // コンパイル オプション GenerateInMemory = true // アセンブリをメモリ内で生成 }, compilationUnits: codeCompileUnit // コンパイル単位 ); return compilerResults.CompiledAssembly; // コンパイルされたアセンブリを返す } static void Main() { // Item クラスを含む名前空間を CodeDOM で生成 var itemClassCodeDom = ItemClassCodeDom(); // Item クラスのソースコードを生成 var code = GenerateCode(itemClassCodeDom); Console.WriteLine(code); // Item クラスを、アセンブリとしてコンパイル var itemAssembly = CompileAssembly(codeNamespace: itemClassCodeDom); // Item クラス アセンブリのテスト TestItemAssembly(itemAssembly); } // Item クラス アセンブリのテスト static void TestItemAssembly(Assembly itemAssembly) { // Item クラスを、アセンブリから取得 var itemType = itemAssembly.GetTypes().First(); // Item クラスのインスタンスを動的に生成 dynamic item = Activator.CreateInstance(itemType); // Item クラスの Price プロパティのテスト item.Price = 2980; Console.WriteLine(item.Price); // Item クラスの ToString メソッドのテスト Console.WriteLine(item); } }
実行してみると、次のように表示され、Price プロパティとToString メソッドが正常に使えるのが分かる。
2980 2980円
まとめ
今回は、CodeDOM を使った動的にクラスを生成した。CodeDOM を使うことで、クラスを実行時に作ることができる。
関連
関連記事
[C#][式木][LINQ] IQueryable な Twitter のタイムライン クラスと LINQ プロバイダー
「C# Advent Calendar 2014」の12日目の記事。 前の記事 ...
[C#][ラムダ式][式木] Expression として扱えるラムダ式と扱えないラムダ式
前回、「匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味 ...
[C#][dynamic] 列挙型 (enum) の列挙子の動的な取得など
今回は、列挙型 (enum) の列挙子の取得などについて。 ■ 列挙型 (enu ...
[C#][Roslyn] C# 6.0 プレビューのスライド公開
※ 「 Hokuriku.NET Vol.15 in FUKUI 開催」の続き。 ...
LINQ to SQL
Chica's Blog - ScottGuさんのブログ翻訳 より 「LINQ ...
ディスカッション
コメント一覧
まだ、コメントがありません