[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 を使うことで、クラスを実行時に作ることができる。
関連
関連記事
Visual Studio 2017 のライブ ユニット テスト機能による「車窓からの TDD」
※ Visual Studio Advent Calendar 2016 の12 ...
[C#][Design Pattern][DynamicObject][dynamic] C# による Observer パターンの実装 その6 – DynamicObject を使ってオブザーバーを作る
※ C# Advent Calender 2012 の 25日目のエントリー。 ...
[C#][.NET] メタプログラミング入門 – 応用編 – オブジェクトの文字列変換のメタプログラミング (Reflection.Emit 編)
※ 「 メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプ ...
[Event] BuriKaigi 2021 を開催しました
毎年冬に富山で開催されている BuriKaigi (*)。 COVID-19 の ...
[C#][.NET][CodeDOM] メタプログラミング入門 – CodeDOM による Hello world!
※ 「 メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプ ...
ディスカッション
コメント一覧
まだ、コメントがありません