[C#][.NET][CodeDOM] メタプログラミング入門 – CodeDOM による Hello world!

Metasequoia

※ 「[C#][.NET] メタプログラミング入門 – 応用編 – オブジェクトの文字列変換のメタプログラミング (パフォーマンスのテスト)」の続き。

CodeDOM による動的コード生成

これまで、Reflection.Emit、式木、Roslyn による動的コード生成を試してきた。これらは、比較的新しい方法だが、実は .NET Framework には初期の頃から動的コード生成を行う仕組みが備わっていた。

それが、CodeDOM (Code Document Object Model) だ。

System.CodeDom 名前空間
System.CodeDom.Compiler 名前空間にあるクラスを使うことで、C#
や Visual Basic.NET 等のコードを生成することができる。

今回は、CodeDOM を使って Hello world! を表示する簡単なプログラムを生成してみよう。

CodeDOM による Hello world!

CodeDOM を使って、次の手順でプログラムを組み立てていく。

  1. 名前空間の生成
  2. 名前空間への System 名前空間のインポート
  3. Program クラスの生成
  4. Program クラスの CodeDomHelloWorldDemo 名前空間への追加
  5. Main メソッドの生成
  6. Main メソッドの中身に文を追加
  7. Program クラスに Main メソッドを追加

では、やってみよう。
System.CodeDom 名前空間を使用する。

using System.CodeDom;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
// CodeDomHelloWorldDemo 名前空間
var nameSpace = new CodeNamespace(name: "CodeDomHelloWorldDemo");
// System 名前空間のインポート
nameSpace.Imports.Add(new CodeNamespaceImport(nameSpace: "System"));
// Program クラス
var programClass = new CodeTypeDeclaration(name: "Program");
// Program クラスの CodeDomHelloWorldDemo 名前空間への追加
nameSpace.Types.Add(programClass);
// Main メソッド
var mainMethod   = new CodeMemberMethod { Attributes = MemberAttributes.Static, Name = "Main" };
// Main メソッドの中身に文を追加
mainMethod.Statements.Add(
new CodeMethodInvokeExpression( // 関数呼び出し式
targetObject: new CodeSnippetExpression("Console")       , // オブジェクト名: Console.
methodName  : "WriteLine"                                , // メソッド名    : WriteLine
parameters  : new CodePrimitiveExpression("Hello world!")  // 引数          : ("Hello world!")
)
);
// Main メソッドの中身に文を追加
mainMethod.Statements.Add(
new CodeMethodInvokeExpression( // 関数呼び出し式
targetObject: new CodeSnippetExpression("Console"), // オブジェクト名: Console.
methodName  : "ReadKey"                             // メソッド名    : ReadKey()
)
);
// Program クラスに Main メソッドを追加
programClass.Members.Add(mainMethod);
return nameSpace;
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
}
}

この HelloWorldCodeDom メソッドで、Hello world! を表示する Main メソッドを含む名前空間を生成したことになる。

CodeDOM によるソースコードの生成

次に、上で作った名前空間から CodeDOM を使って C# のソースコードを生成してみる。
System.CodeDom.Compiler 名前空間を使用する。

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
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()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
}
}

実行してみると、次のように C# のソースコードが表示される。

namespace CodeDomHelloWorldDemo
{
using System;
public class Program
{
static void Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}

ちなみに、上記 GenerateCode メソッド中の "C#" とある部分を "VB" と置き換えて実行してみると、次のように Visual Basic.NET のソースコードとなる。

Imports System
Namespace CodeDomHelloWorldDemo
Public Class Program
Shared Sub Main()
Console.WriteLine("Hello world!")
Console.ReadKey
End Sub
End Class
End Namespace

"C#" を "JScript" に置き換えた場合は、次のようになる。

//@cc_on
//@set @debug(off)
import System;
package CodeDomHelloWorldDemo
{
public class Program
{
private static function Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}

CodeDOM によるアセンブリの生成

では次に、CodeDOM で生成した名前空間をコンパイルしてアセンブリを生成してみよう。

先程までのプログラムに、更に書き足して、次のようにする。

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
…… 同じなので省略 ……
}
// 名前空間を実行可能アセンブリへコンパイル
static void CompileExecutableAssembly(CodeNamespace codeNamespace, string outputAssemblyName)
{
var codeCompileUnit = new CodeCompileUnit();     // コンパイル単位
codeCompileUnit.Namespaces.Add(codeNamespace);   // コンパイル単位に名前空間を追加
// 実行可能アセンブリへコンパイル
CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom(
options         : new CompilerParameters {   // コンパイル オプション
OutputAssembly     = outputAssemblyName, // 出力アセンブリのファイル名
GenerateExecutable = true                // 実行可能なアセンブリを生成
},
compilationUnits: codeCompileUnit                     // コンパイル単位
);
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
// Hello world プログラムを、実行可能アセンブリとしてコンパイル
const string outputAssemblyName = "CodeDomHelloWorldDemo.exe";
CompileExecutableAssembly(codeNamespace: HelloWorldCodeDom(), outputAssemblyName: outputAssemblyName);
Process.Start(fileName: outputAssemblyName); // 生成された実行可能アセンブリを実行
}
}

実行してみると、"CodeDomHelloWorldDemo.exe" が立ち上がり、Hello world! と表示される。

Hello world!

まとめ

今回は、CodeDOM を使った動的コード生成を行ってみた。
次回も CodeDOM を使ってみよう。