Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

CodeDomによる実行時コード生成術

アプリケーション実行時に実行コードを生成する動的技術を探る

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/10/20 00:00

本稿では、Microsoft .NET Frameworkが提供する動的なコード生成サービス「CodeDom(Code Document Object Model)」について解説します。CodeDomを利用すると、開発者はプログラミング言語に依存せずに、.NETに対応したプログラミングの論理的な構造を表現することができ、必要に応じてソースコードや実行可能なアセンブリに変換することができます。

目次

はじめに

 CodeDomは、.NET Frameworkが提供する高度な実行時コード生成ライブラリです。CodeDomを利用することで、開発者は容易に実行時にコードを生成するアプリケーションを開発することができ、動的に実行コードを管理することができます。System.Reflection名前空間によるリフレクションもまた、実行時レベルでのコード制御を行う強力なライブラリですが、CodeDomはリフレクションとは異なり、.NETのプログラム構造を論理的に表現できるという特徴を持ちます。

 既存の実行可能コードを実行時に解析する場合、リフレクションが大きな威力を発揮します。これに対し、CodeDomは、特定のプログラミング言語に依存しない形で、抽象的にプログラムの構造を表現するデータを構築したいときに大きな威力を発揮します。.NETアプリケーションはVisual Basic .NET、C#、C++/CLIなど、多くの言語から開発することができますが、その結果として出力されるのは「MSIL」と呼ばれる中間言語です。CodeDomは、これら個別の言語には依存しない形で、名前空間、クラス、メソッド、プロパティ、変数、イベント、デリゲートなどのさまざまな要素を表現することが可能です。

 CodeDomは、その性質から「プログラミング言語によるソースコード」と「実行可能コード」の中間的な位置に存在する技術と考えてよいでしょう。CodeDomは、プログラミング言語に依存しない形でプログラムの構造を論理的に表現するため、CodeDomによって表現されたデータ構造から、ソースコードを生成したり、または実行可能コードを生成することができます。例えば、C#言語のソースコードをCodeDomのデータモデルに変換し、これをVisual Basic .NET言語のソースコードに出力するということも可能です。もちろん、CodeDomはアプリケーションが実行時にコードを生成することも可能としているため、Visual StudioのGUIデザイナのように、ビジュアルな開発環境を作る手助けにもなるでしょう。

ソースコードの生成

 まず、最初にCodeDomを利用したプログラムで何ができるのかを感じ取っていただくために、簡単なソースコードを生成するサンプルを作成してみましょう。ここで作成するソースコードは、文字列リテラルやユーザーの手入力によって作成するのではなく、アプリケーションが実行時にプログラムの論理構造をCodeDomで作成し、最終的にデータをソースコードに変換するというものです。

 実行時に動的にコードを生成するには、System.CodeDom名前空間にある各種クラスを利用して、プログラムの論理構造をインスタンス化して関連付けなければなりません。簡単に言ってしまうならば、CodeDomの作業はプログラミング言語の代わりにオブジェクトを用いてプログラムの構造を表現するということなのです。

プログラム構造の定義

 まずはじめに、すべてのプログラム構造を束ねるコンテナとなるSystem.CodeDom.CodeCompileUnitクラスのオブジェクトが必要になります。

System.CodeDom.CodeCompileUnitクラス(継承階層)
System.Object
  System.CodeDom.CodeObject
    System.CodeDom.CodeCompileUnit
System.CodeDom.CodeCompileUnitクラス(C#での構文)
[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
[ComVisibleAttribute(true)]
public class CodeCompileUnit : CodeObject

 このクラスのオブジェクトは、プログラミング言語の1つのソースコードに相当する役割を持ちます。つまり、CodeCompileUnitは1つのコンパイル単位を表現します。まず最初に、このクラスのインスタンスを作成します。インスタンスを作成した既定の状態では、プログラムの構造は何も作られていませんが、この状態でも変換は可能です。

 コンパイル単位のトップレベルのメンバは常に名前空間です。よって、クラスやメソッドなどのより具体的なロジックを表現するには、最初に名前空間を作成しなければなりません。名前空間を表現するにはSystem.CodeDom.CodeNamespaceクラスを利用します。

System.CodeDom.CodeNamespaceクラス(継承階層)
System.Object
  System.CodeDom.CodeObject
    System.CodeDom.CodeNamespace
System.CodeDom.CodeNamespaceクラス(C#での構文)
[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
[ComVisibleAttribute(true)]
public class CodeNamespace : CodeObject

 CodeNamespaceのオブジェクトは1つの名前空間の宣言に対応します。名前空間の識別子は、CodeNamespaceクラスのコンストラクタから設定するか、またはNameプロパティから設定・取得することができます。

 コンパイル単位に名前空間を設定するには、CodeCompileUnitオブジェクトのNamespacesプロパティから取得することができるSystem.CodeDom.CodeNamespaceCollectionオブジェクトに追加します。CodeNamespaceCollectionは、IListICollectionIEnumerableインターフェイスを実装するコレクションなので、オブジェクトの追加や取得方法は、一般的なコレクションAPIと同じです。Add()メソッドからCodeNamespaceを追加してください。

オブジェクトを任意のプログラミング言語のソースコードに変換する

 ここでは、ソースを生成するサンプルを目的としているため、プログラム構造の定義はこの程度にしておいて、設定したCodeCompileUnitオブジェクトを任意のプログラミング言語のソースコードに変換する作業に入りましょう。

 CodeDomのデータを別の形に変換する作業はSystem.CodeDom.Compiler.CodeDomProviderクラスが行ってくれます。ここから、ソースコードを生成したり、実行可能なアセンブリを生成することができます。

System.CodeDom.Compiler.CodeDomProviderクラス(継承階層)
System.Object
  System.MarshalByRefObject
    System.ComponentModel.Component
      System.CodeDom.Compiler.CodeDomProvider
System.CodeDom.Compiler.CodeDomProviderクラス(C#での構文)
[ComVisibleAttribute(true)]
public abstract class CodeDomProvider : Component

 宣言を見ての通り、CodeDomProviderクラスは抽象クラスです。このクラスは基本的な操作を提供していますが、具体的にCodeCompileUnitオブジェクトをどのような形に変換するかはサブクラスに委ねられています。つまり、C#のソースコードを生成したい場合は、C#用のCodeDomProviderの実装を用意し、Visual Basic .NETのソースコードを生成したい場合は、Visual Basic .NET用のCodeDomProviderの実装を用意すればよいのです。

 NET Frameworkには、CodeDomProviderクラスの実装として次のようなものが用意されています。

  • Microsoft.CSharp.CSharpCodeProvider …… C#言語の実装
  • Microsoft.JScript.JScriptCodeProvider …… JScriptの実装
  • Microsoft.VisualBasic.VBCodeProvider …… Visual Basicの実装

 ここでは、CSharpCodeProviderのインスタンスを生成し、C#言語のソースコードを出力してみましょう。CodeDomProviderのインスタンスを取得することができれば、GenerateCodeFromCompileUnit()メソッドを用いてCodeCompileUnitオブジェクトを任意のTextWriterに出力することができます。ファイル出力ストリームを用意してテキストファイルに書き出すこともできますし、Console.Outを渡せば標準出力に表示できます。

CodeDomProviderクラスのGenerateCodeFromCompileUnit()メソッド
public virtual void GenerateCodeFromCompileUnit (
    CodeCompileUnit compileUnit, TextWriter writer,
    CodeGeneratorOptions options
)

 compileUnitには、ソースコードに変換するCodeCompileUnitオブジェクトを、writerには出力先のTextWriterオブジェクトを指定します。最後のoptionsパラメータは、コードを生成するために使用するオプションを表すSystem.CodeDom.Compiler.CodeGeneratorOptionsクラスのオブジェクトを指定します。ここでは、既定の状態で問題はないので、CodeGeneratorOptionsのインスタンスを生成し、特にプロパティは変更しないでそのままoptionsパラメータに渡すことにしましょう。

Sample00(test.cs)
using System;
using System.CodeDom;
using System.CodeDom.Compiler;

using Microsoft.CSharp;

class Test {
    public static void Main() {
        CodeCompileUnit compileUnit = new CodeCompileUnit();
        CodeNamespace name = new CodeNamespace("Sample00");
        compileUnit.Namespaces.Add(name);

        CSharpCodeProvider provider = new CSharpCodeProvider();
        CodeGeneratorOptions option = new CodeGeneratorOptions();
        provider.GenerateCodeFromCompileUnit(compileUnit,
         Console.Out, option);
    }
}
Sample00の実行結果
//-------------------------------------------------------------------
// <auto-generated>
// このコードはツールによって生成されました。
// ランタイム バージョン:2.0.50727.42
//
// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
// コードが再生成されるときに損失したりします。
// </auto-generated>
//-------------------------------------------------------------------

namespace Sample00 {

}

 Sample00を実行すると、標準出力にC#言語のソースコードが出力されます。このコードはCodeCompileUnitオブジェクトに設定されている名前空間に従って言語化されています。

 このように、CodeDomとは、プログラミング言語で記述するプログラムの構造やロジックをオブジェクトで抽象的に表現する手段です。実行時に、オブジェクトで構築された論理的なプログラム構造は、CodeDomProviderによって変換することができます。

よりプログラムらしくするには

 Sample00では、単純に名前空間だけを定義していましたが、もう少しプログラムらしい構造を作成してソースコードに変換してみましょう。CodeDomでは、Sample00の要領で、さらに名前空間に型を表すオブジェクトを追加したり、型に対してフィールドやメソッドなどのメンバを追加することも可能です。さらに、メソッドには細かいステートメントを設定するといったことも、CodeDomだけでプログラミングできます。

 CodeNamespaceオブジェクトには、クラスや構造体、インターフェイス、列挙体などの型を表すオブジェクトをTypesプロパティから追加することができます。Typesプロパティは、型を表すSystem.CodeDom.CodeTypeDeclarationオブジェクトの配列を管理するコレクションを返します。

System.CodeDom.CodeTypeDeclarationクラス(継承階層)
System.Object
  System.CodeDom.CodeObject
    System.CodeDom.CodeTypeMember
      System.CodeDom.CodeTypeDeclaration
System.CodeDom.CodeTypeDeclarationクラス(C#での構文)
[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
[ComVisibleAttribute(true)]
public class CodeTypeDeclaration : CodeTypeMember

 CodeTypeDeclarationは、名前空間が保有するメンバを表すオブジェクトであり、CodeTypeDeclarationのインスタンスが、1つの型に対応します。型の識別子は、このクラスのコンストラクタから設定するか、またはNameプロパティから設定・取得することができます。

 さらに、型にはフィールドやメソッド、プロパティなどのメンバを設定することができます。型のメンバはCodeTypeDeclarationクラスのMembersプロパティから設定します。このプロパティは、メンバを表すSystem.CodeDom.CodeTypeMemberクラスのオブジェクトを管理するコレクションオブジェクトを返します。

System.CodeDom.CodeTypeMemberクラス(継承階層)
System.Object
  System.CodeDom.CodeObject
    System.CodeDom.CodeTypeMember
System.CodeDom.CodeTypeMemberクラス(C#での構文)
[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)]
[ComVisibleAttribute(true)]
public class CodeTypeMember : CodeObject

 CodeTypeMemberクラスは、メンバを表すクラスのルートクラスです。フィールドやメソッドなど、具体的なメンバを表すクラスはCodeTypeMemberクラスを継承しています。例えば、通常のメソッドを追加するにはSystem.CodeDom.CodeMemberMethodクラスのインスタンスを生成してMembersプロパティからコレクションに追加します。

 メンバの識別子はNameプロパティから設定します。フィールドであればフィールド名、メソッドであればメソッド名となります。ただし、識別子の解釈は言語やメンバの性質によって異なります。例えば、C#言語では、コンストラクタの名前はクラスの名前と同一でなければならないという制約があるため、コンストラクタに対するNameプロパティは無視されます。

CodeTypeMemberクラス Nameプロパティ
public string Name { get; set; }

 ここでは、すべてのコードの表現方法を解説することはできませんが、CodeDomでは、このようにプログラムの要素を表すさまざまなクラスが用意されています。CodeDomの機能を駆使したアプリケーションを開発すれば、コードを記述しない、ビジュアルなプログラミング開発環境などを実現することができるかもしれません。

Sample01(test.cs)
using System;
using System.CodeDom;
using System.CodeDom.Compiler;

using Microsoft.CSharp;

class Test {
    public static void Main() {
        CodeCompileUnit compileUnit = new CodeCompileUnit();
        CodeNamespace name = new CodeNamespace("Sample01");
        CodeTypeDeclaration type = new CodeTypeDeclaration("Test");
        CodeMemberMethod method = new CodeMemberMethod();

        method.Name = "M";

        type.Members.Add(method);
        name.Types.Add(type);
        compileUnit.Namespaces.Add(name);

        CSharpCodeProvider provider = new CSharpCodeProvider();
        CodeGeneratorOptions option = new CodeGeneratorOptions();
        provider.GenerateCodeFromCompileUnit(compileUnit,
         Console.Out, option);
    }
}
Sample01の実行結果
//-------------------------------------------------------------------
//
//     このコードはツールによって生成されました。
//     ランタイム バージョン:2.0.50727.42
//
//     このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
//     コードが再生成されるときに損失したりします。
//
//-------------------------------------------------------------------

namespace Sample01 {

    public class Test {

        private void M() {
        }
    }
}

 Sample01は、名前空間にTestクラスを追加し、さらにTestクラスにはM()メソッドを追加しています。実行結果を見れば、オブジェクトの構造に従ってC#言語のソースコードが適切に生成されていることが確認できます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 赤坂 玲音(アカサカ レオン)

    平成13年度「全国高校生・専門学校生プログラミングコンテスト 高校生プログラミングの部」にて最優秀賞を受賞。 2005 年度~ Microsoft Most Variable Professional Visual Developer - Visual C++。 プログラミング入門サイト Wis...

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5