はじめに
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.Object System.CodeDom.CodeObject System.CodeDom.CodeCompileUnit
[SerializableAttribute] [ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)] [ComVisibleAttribute(true)] public class CodeCompileUnit : CodeObject
このクラスのオブジェクトは、プログラミング言語の1つのソースコードに相当する役割を持ちます。つまり、CodeCompileUnit
は1つのコンパイル単位を表現します。まず最初に、このクラスのインスタンスを作成します。インスタンスを作成した既定の状態では、プログラムの構造は何も作られていませんが、この状態でも変換は可能です。
コンパイル単位のトップレベルのメンバは常に名前空間です。よって、クラスやメソッドなどのより具体的なロジックを表現するには、最初に名前空間を作成しなければなりません。名前空間を表現するにはSystem.CodeDom.CodeNamespace
クラスを利用します。
System.Object System.CodeDom.CodeObject System.CodeDom.CodeNamespace
[SerializableAttribute] [ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)] [ComVisibleAttribute(true)] public class CodeNamespace : CodeObject
CodeNamespace
のオブジェクトは1つの名前空間の宣言に対応します。名前空間の識別子は、CodeNamespace
クラスのコンストラクタから設定するか、またはName
プロパティから設定・取得することができます。
コンパイル単位に名前空間を設定するには、CodeCompileUnit
オブジェクトのNamespaces
プロパティから取得することができるSystem.CodeDom.CodeNamespaceCollection
オブジェクトに追加します。CodeNamespaceCollection
は、IList
、ICollection
、IEnumerable
インターフェイスを実装するコレクションなので、オブジェクトの追加や取得方法は、一般的なコレクションAPIと同じです。Add()
メソッドからCodeNamespace
を追加してください。
オブジェクトを任意のプログラミング言語のソースコードに変換する
ここでは、ソースを生成するサンプルを目的としているため、プログラム構造の定義はこの程度にしておいて、設定したCodeCompileUnit
オブジェクトを任意のプログラミング言語のソースコードに変換する作業に入りましょう。
CodeDomのデータを別の形に変換する作業はSystem.CodeDom.Compiler.CodeDomProvider
クラスが行ってくれます。ここから、ソースコードを生成したり、実行可能なアセンブリを生成することができます。
System.Object System.MarshalByRefObject System.ComponentModel.Component System.CodeDom.Compiler.CodeDomProvider
[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
を渡せば標準出力に表示できます。
public virtual void GenerateCodeFromCompileUnit ( CodeCompileUnit compileUnit, TextWriter writer, CodeGeneratorOptions options )
compileUnit
には、ソースコードに変換するCodeCompileUnit
オブジェクトを、writer
には出力先のTextWriter
オブジェクトを指定します。最後のoptions
パラメータは、コードを生成するために使用するオプションを表すSystem.CodeDom.Compiler.CodeGeneratorOptions
クラスのオブジェクトを指定します。ここでは、既定の状態で問題はないので、CodeGeneratorOptions
のインスタンスを生成し、特にプロパティは変更しないでそのままoptions
パラメータに渡すことにしましょう。
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); } }
//------------------------------------------------------------------- // <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.Object System.CodeDom.CodeObject System.CodeDom.CodeTypeMember System.CodeDom.CodeTypeDeclaration
[SerializableAttribute] [ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)] [ComVisibleAttribute(true)] public class CodeTypeDeclaration : CodeTypeMember
CodeTypeDeclaration
は、名前空間が保有するメンバを表すオブジェクトであり、CodeTypeDeclaration
のインスタンスが、1つの型に対応します。型の識別子は、このクラスのコンストラクタから設定するか、またはName
プロパティから設定・取得することができます。
さらに、型にはフィールドやメソッド、プロパティなどのメンバを設定することができます。型のメンバはCodeTypeDeclaration
クラスのMembers
プロパティから設定します。このプロパティは、メンバを表すSystem.CodeDom.CodeTypeMember
クラスのオブジェクトを管理するコレクションオブジェクトを返します。
System.Object System.CodeDom.CodeObject System.CodeDom.CodeTypeMember
[SerializableAttribute] [ClassInterfaceAttribute(ClassInterfaceType.AutoDispatch)] [ComVisibleAttribute(true)] public class CodeTypeMember : CodeObject
CodeTypeMember
クラスは、メンバを表すクラスのルートクラスです。フィールドやメソッドなど、具体的なメンバを表すクラスはCodeTypeMember
クラスを継承しています。例えば、通常のメソッドを追加するにはSystem.CodeDom.CodeMemberMethod
クラスのインスタンスを生成してMembers
プロパティからコレクションに追加します。
メンバの識別子はName
プロパティから設定します。フィールドであればフィールド名、メソッドであればメソッド名となります。ただし、識別子の解釈は言語やメンバの性質によって異なります。例えば、C#言語では、コンストラクタの名前はクラスの名前と同一でなければならないという制約があるため、コンストラクタに対するName
プロパティは無視されます。
public string Name { get; set; }
ここでは、すべてのコードの表現方法を解説することはできませんが、CodeDomでは、このようにプログラムの要素を表すさまざまなクラスが用意されています。CodeDomの機能を駆使したアプリケーションを開発すれば、コードを記述しない、ビジュアルなプログラミング開発環境などを実現することができるかもしれません。
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); } }
//------------------------------------------------------------------- // // このコードはツールによって生成されました。 // ランタイム バージョン:2.0.50727.42 // // このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 // コードが再生成されるときに損失したりします。 // //------------------------------------------------------------------- namespace Sample01 { public class Test { private void M() { } } }
Sample01は、名前空間にTest
クラスを追加し、さらにTest
クラスにはM()
メソッドを追加しています。実行結果を見れば、オブジェクトの構造に従ってC#言語のソースコードが適切に生成されていることが確認できます。