CodeDomによるHello World

MSIL/ILによるHello Worldの次はCodeDomのHello Worldの実現。
以下のようなソースコードをCodeDomにより生成、コンパイル、実行してみる。

生成するソースコード

namespace CodeDomTest {
    using System;
    public class HelloWorld 
    {
        [STAThread()]
        public static void Main()
        {
            System.Console.WriteLine("Hello World");
        }
    }
}

HelloWorldクラスのソースコードを生成するCodeDomによるアプリケーション

using System;
using System.Text;
using System.IO;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

namespace CodeDomTest
{    
    class Application 
    {        
        [STAThread]
        static void Main(string args)
        {            
            //コードの入れ物、コンパイルの単位となるコンパイルユニットを生成
            CodeCompileUnit compileUnit = new System.CodeDom.CodeCompileUnit();

            //ネームスペースを追加
            CodeNamespace helloWorldNamespace = new CodeNamespace("CodeDomTest");
            compileUnit.Namespaces.Add(helloWorldNamespace);

            //インポートを追加
            helloWorldNamespace.Imports.Add(new CodeNamespaceImport("System"));
            //Applicationクラスを定義        
            CodeTypeDeclaration applicationClass = new CodeTypeDeclaration("HelloWorld");
            applicationClass.IsClass = true;
            helloWorldNamespace.Types.Add(applicationClass);

            //staticなMainメソッドを生成
            CodeMemberMethod mainMethod = new CodeMemberMethod();
            mainMethod.Name = "Main";
            mainMethod.Attributes = MemberAttributes.Static | MemberAttributes.Public;
            mainMethod.CustomAttributes.Add(new CodeAttributeDeclaration("STAThread"));
            applicationClass.Members.Add(mainMethod);

            //"Hello World" 出力
            CodeMethodInvokeExpression printHelloWorld = 
                new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(Console))
                , "WriteLine", new CodeExpression {new CodePrimitiveExpression("Hello World") });
            mainMethod.Statements.Add(printHelloWorld);

            //ソースコード生成
            TextWriter writer = new StreamWriter(new FileStream("HelloWorld.cs", FileMode.Create));
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            try
            {
                ICodeGenerator codeGenerator = codeProvider.CreateGenerator();
                codeGenerator.GenerateCodeFromCompileUnit(compileUnit, writer, null);
            }
            finally
            {
                writer.Close();
            }

            //生成したコードをコンパイル
            ICodeCompiler codeCompiler = codeProvider.CreateCompiler();
            CompilerParameters cparams = new CompilerParameters();
            cparams.ReferencedAssemblies.Add("system.dll"); 
            cparams.GenerateExecutable = true; 
            CompilerResults result = codeCompiler.CompileAssemblyFromFile(cparams, "HelloWorld.cs");

            //コンパイル結果からアセンブリを生成
            Assembly assembly = result.CompiledAssembly;
            object compiled = assembly.CreateInstance("CodeDomTest.HelloWorld");

            //アセンブリを実行
            MethodInfo methodInfo = compiled.GetType().GetMethod("Main");
            methodInfo.Invoke(compiled, null);
        }
    }
}

例によってコンパイル時のエラー処理等ははしょっているので試したい方は注意。生成されたソースコード"HelloWorld.cs"は実行ディレクトリに直接出力されるので内容を確認できます。

う〜ん..CodeDomはソースコードの殆ど全てをオブジェクトとして抽象化して扱うのでそれぞれの意味は解り易いんだけれどHello Worldを作るのにこれだけのコード量が必要なのは引きますね。やはり既存の型情報をベースにして別な型を作り出す時のテンプレート等を作るツールとして使うほうが生産的かも。

前回のILのEmitや今回のCodeDomのようにプログラムが他のプログラムを生成するのを専門的にはgenerative programming (生成的プログラミング)といいます。GenericsやAOP等でも使われている重要な技術ですね。