.NET Framework Visual Designers におけるコード生成のカスタマイズ

Shawn Burke
Microsoft Corporation

March 2002

**要約 :**Microsoft .NET におけるさまざまな種類のコード生成について説明し、コンポーネントの作成者がコード生成プロセスに参加する方法を示します。

MSDN Code Center から buttonarraysample.exe をダウンロードします。

目次

はじめに
コード生成の基本概念
入れ子になったオブジェクトの生成
カスタム型に対するコード生成
バイナリ シリアル化の使用
コンポーネントに初期化を認識させる
デザイン時に固有の情報のシリアル化
コード生成の制御
まとめ

はじめに

Microsoft® .NET Framework のデザイン時アーキテクチャを開発する場合、Microsoft ではバイナリなどによる個人的な解決方法ではなく、ソース コードを継続的にユーザー コードとして使用することにしました。

コードの永続性によって、ユーザーはデザイナが出力するコードから学習し、必要に応じて Microsoft Visual Studio® .NET 以外の環境で容易にプロジェクトを構築できるようになります。 また、詳細なシナリオおよびコンポーネントのためのカスタマイズを理解し、アクセスできるようになります。

大半の場合、カスタム コンポーネントの状態を永続化するコードはデザイナによって自動的に生成されます。 ただし、コントロールの作成者はコード生成エンジンに詳細な情報を指定したり、特定の型の処理方法をカスタマイズすることが必要になる場合があります。 ここでは、さまざまな種類のコード生成について説明し、コンポーネントの作成者がコード生成プロセスに参加する方法を示します。

コード生成の基本概念

簡潔にいうと、コード生成とは、オブジェクトの読み書き可能なパブリック プロパティを検査して、各値をコードに出力する (コードに出力できない場合はリソース ファイルに出力する) ことです。 単純な例として、フォーム上のボタンについて考えてみましょう。 このボタンに対して生成される C# コードを次に示します。

// 
   // button1
   // 
   this.button1 = new System.Windows.Forms.Button();
   this.button1.Location = new System.Drawing.Point(40, 48);
   this.button1.Size = new System.Drawing.Size(104, 48);         
   this.button1.Text = "OK";

明らかに、ボタン クラスは "Location"、"Size"、および "Text" 以外のプロパティを保持しています。 その一方で、デザイン時と実行時の読み込みおよび初期化のパフォーマンスなどのさまざまな理由から、必要以上のプロパティに対するコード生成を回避しようとします。 この場合、オブジェクトのコード生成を出力しないようにするための方法が 2 つあります。 1 つはプロパティに System.ComponentModel.DesignerSerializationVisiblityAttribute を適用します。 既定では、読み書き可能なパブリック プロパティは DesignerSerializationVisiblity.Hidden 値が設定されていない限り、コードが生成されます。

[DesignerSerializationVisibility(
DesignerSerializationVisibility.Hidden)]
   public string StringProp 
   {
      get 
      {
         return stringPropValue;
      }
      set 
      {
         stringPropValue = value;
      }
   }   

この属性を適用すると、このプロパティにコードが生成されることを回避できます。 ただし、実際にはプロパティの既定値の生成を回避したい場合がほとんどです。 これを実現するには 2 つの方法があります。 1 つは System.ComponentModel.DefaultValueAttribute を適用します。 DefaultValueAttribute はプロパティの既定値を示す値を取得します。 プロパティの現在値が既定値と一致する場合は、コードが生成されません。

private string text = "OK";

      [DefaultValue("OK")]
      public string Text 
      { 
         get 
         {
            return this.text;
         }
         set 
         {
            this.text = value;
            Invalidate();
         }
      }

ただし、既定値が単純な値でない場合や、より動的な処理が必要な場合はどうすればよいでしょうか ? たとえば、親コントロールなど、ほかのコンポーネントから値が継承される場合がよくあります。 動的な処理を必要とする場合は、特定のシグネチャ付きのメソッドを追加することによって対処できます。 上記の例を使用した場合のコードを次に示します。

private bool ShouldSerializeText() 
      {
         return Text != "OK";
      }

ShouldSerialize<プロパティ名> という名前のメソッドを追加すると、コード生成エンジンが値をシリアル化するかどうかをコンポーネントに確認します。 ShouldSerialize メソッドはプライベート メソッドとして作成されるため、オブジェクト モデルがばらばらになることはありません。 添付された ButtonArray のサンプルで、これに関するいくつかのコード例が示されています。

入れ子になったオブジェクトの生成

最上位プロパティでは、オブジェクトのすべての状態が利用できるわけではありません。 多くのオブジェクトは、コレクション プロパティなど、入れ子になったオブジェクトに参照を返すプロパティを保持しています。 ただし、.NET フレームワークでは、オブジェクトからコントロールまで大半の型がパブリック プロパティを保持しています。 この場合、コード生成エンジンが異なる扱いをするプロパティとしてマークするにはどうすればよいでしょうか ?

答えは、前述の DesignerSerializationVisiblityAttribute に別の値を指定することです。 この場合は、DesignerSerializationVisiblity.Content を使用してプロパティをマークし、コード ジェネレータにこのプロパティに "立ち寄り" コードを生成するように指示します。 フォーム上の DockPadding プロパティのコード例を次に示します。

this.DockPadding.Left = 5;
   this.DockPadding.Top = 4;
   

フレームワークで調べると、System.Windows.Forms.ScrollableControl.DockPaddingEdges と呼ばれるクラスが存在します。ScollableControl クラスはこの型のプロパティを保持しており、内部にそのインスタンスを格納しています。 プロパティ自体は実際に読み取り専用であるため、DockPaddingEdges の新しいインスタンスで設定することはできません。 DesignerSerializationVisiblity.Content を指定すると、コード生成が DockPadding プロパティの値上で繰り返され、上記のコードが生成されます。

カスタム型に対するコード生成

いくつかのノードが割り当てられているツリービューに対して生成されるコードについて考えてみましょう。

this.treeView1 = new System.Windows.Forms.TreeView();
this.treeView1.Location = new System.Drawing.Point(72, 104);
this.treeView1.Name = "treeView1";
this.treeView1.Nodes.AddRange(
new System.Windows.Forms.TreeNode[] {
      new System.Windows.Forms.TreeNode("Node0"),
      new System.Windows.Forms.TreeNode("Node1")});
                  

インライン展開のサブ項目に対して生成されたコードに注目すると、渡されるラベルのテキストがコンストラクタにあります。 TreeNode クラスを調べると、少数のコンストラクタが存在することがわかります。 正確には 5 つのコンストラクタです。 この場合、コード ジェネレータはどのようにして呼び出すコンストラクタを認識するのでしょうか ? System.ComponentModel.Design.InstanceDescriptor を入力して解決します。 InstanceDescriptor は、クラスの作成時に呼び出すメソッド (コンストラクタまたはそれ以外) をコード生成エンジンに示します。 指定した型の InstanceDescriptor は、その型の TypeConverter から取得されます。 この情報を保持する TypeConverter は、文字列など、ほかの型に値を変換する場合と同様に、値を InstanceDescriptor 型に "変換" できます。

InstanceDescriptor の基本的な役割は、次の 3 つの情報をコード生成エンジンに提供することです。

  1. オブジェクトのインスタンスを取得するために呼び出されるメンバ (通常はコンストラクタですが、静的メソッドの場合もあります)。
  2. オブジェクトの状態を初期化するためにそのメンバに渡される値。
  3. 呼び出しは完全にオブジェクトの状態を示しているか、または永続化が必要か。

上記の TreeNode のサンプルは、コンストラクタによって完全に状態が示されているコード例です。 TreeNode を初期化するために、ほかのプロパティは セットは必要ありません。 ただし、サブオブジェクト自体が非常に多くのプロパティを保持している場合は、通常、初期化に必要なコンストラクタの全置換を常に行うことは困難であると同時に非現実的です。 次のコードは、サブ項目がコンストラクタによって完全には初期化されていない例です。

new System.Windows.Forms.ListViewItem listViewItem1 = 
new ListViewItem(new string[] { "a"}, 
-1, 
System.Drawing.Color.Empty, 
System.Drawing.Color.Red, 
null);
this.listView1 = new System.Windows.Forms.ListView();         
listViewItem1.Checked = true;
listViewItem1.StateImageIndex = 1;
this.listView1.Name = "listView1";
this.listView1.Items.AddRange(
new System.Windows.Forms.ListViewItem[] {listViewItem1});
         
         

"listItem1" に対して生成されたコード行を使用して、インライン展開の "listViewItem1" に対して生成されたコード行に注目します。 コード ジェネレータは指定されたコンストラクタを使用しますが、値が変更されているその他のプロパティに対してもコードを生成しています。 InstanceDescriptor を使用してこのプロセスを管理します。

InstanceDescriptor クラス自体は単純です。 このクラスは、System.Reflection.MemberInfo、メンバ呼び出しが完全にオブジェクトの状態を示しているかどうかを示すブール値、およびパラメータとしてシリアル化されるオブジェクトの配列で構成されます。 先に述べたように、InstanceDescriptor をクラスに関連付けるには TypeDescriptor を使用します。 System.Drawing.Point の InstanceDescriptor のコード例を次に示します。

public class PointConverter : TypeConverter 
{
   public override bool CanConvertTo(
ITypeDescriptorContext context, 
Type destinationType) 
   {
      if (destinationType == typeof(InstanceDescriptor)) 
         return true;
      return base.CanConvertTo(context, destinationType);
   }

public override object ConvertTo(
ITypeDescriptorContext context, 
CultureInfo culture, 
object value, 
Type destinationType) 
{
   // other TypeConverter operations go here...
   //
   if (destinationType == typeof(InstanceDescriptor) && 
value is Point) 
   {
      Point pt = (Point)value;

      ConstructorInfo ctor = 
typeof(Point).GetConstructor(
new Type[] {typeof(int), typeof(int)});
      if (ctor != null) 
      {
         return new 
InstanceDescriptor(
ctor, 
new object[] {pt.X, pt.Y});
}
}
      return base.ConvertTo(context, culture, value, destinationType);      
}

上記のコードは単純です。 2 つの整数値を取る Point 上でコンストラクタを選択し、次に初期化引数として使用するこれらの値を保持するために InstanceDescriptor を作成しています。 添付されたサンプル コードでは、InstanceDescriptor を利用した別の例が示されています。

バイナリ シリアル化の使用

すべての型がコード生成に適合するわけではありません。 たとえば、ビットマップをコードにシリアル化するにはどうすればよいでしょうか ? ビットマップは文字列としてエンコードできますが、中くらいのサイズのビットマップの文字列でさえ、ファイルの残りのコード量より多くなる可能性があります。 デザイナではこの問題を解決するために、バイナリ型をリソース ファイルに永続化します。リソース ファイルでは、これらの値は.NET Framework のマニフェストにプッシュされます。 さらに、デザイナはアプリケーションが初期化されるときに、アセンブリのマニフェストから値を読み込みます。 フォームのバックグラウンドにイメージを表示する場合に生成されるコード例を次に示します。

System.Resources.ResourceManager resources = 
new System.Resources.ResourceManager(typeof(form3));

this.BackgroundImage = 
   ((System.Drawing.Image)
(resources.GetObject("$this.BackgroundImage")));
this.ClientSize = new System.Drawing.Size(292, 273);
this.Name = "form3";
this.Text = "Form2";

ここでわかるとおり、値がコード以外の場所に格納されています。 この処理は、生成コードとしてのシリアル化が難しい多くのデータ型にとって有益です。 型をシリアル化する場合、コード ジェネレータは次の順序に従います。

  1. 組み込みのプリミティブ型 (文字列、ブール、整数、浮動小数点など) のシリアル化
  2. カスタム型に対する InstanceDescriptor のシリアル化
  3. Serializable (シリアル化可能) としてマークされている型に対するバイナリ シリアル化

型を "Serializable (シリアル化可能) としてマークする" にはどうすればいいでしょう ? これには 2 つの方法があります。 最も簡単な方法は、System.SerializableAttribute をクラスに追加してシリアル化を自動化し、クラス内ですべてのフィールド値をシリアル化することです。 シリアル化したくないフィールド (ハンドルなどの動的な情報) がある場合に、System.NonSerializedAttribute を使用してこれらのフィールドをマークします。 以下に単純な例を示します。

[Serializable]
   public class SomeClass 
   {
      private string s;
      private int    i;
      [NonSerialized]
      private IntPtr handle;
   }

クラスを Serializable としてマークする 2 つ目の方法は、ISerializable の実装です。 これによってクラスの作成者はクラスのシリアル化方法を細かく制御できるようになります。 逆シリアル化のための特別なコンストラクタのシグネチャに注目します。これはプライベートに設定されています。

public class SomeClass : ISerializable
{
   private string s;
   private int    i;
   private IntPtr handle;
   public SomeClass(string s, int i) 
   {
   }

   private SomeClass(
SerializationInfo info,
SerializationContext context) 
   {
      // Serialization with ISerializable expects
      // a constructor like this to deserialize
      // an object.
      // We know what we stored below,
      // so fetch those values
      s = info.GetString("stringValue");
      i = info.GetInt32("intValue");
   }

   void ISerializable.GetObjectData(
      SerializationInfo info,
      StreamingContext context
      ) 
   {
      // push the values we want to store
      // into the SerializationInfo
      info.AddValue("stringValue", s);
      info.AddValue("intValue", i);
   }
}

これら2 つの方法のいずれかを実装することによって、クラスがバイナリ リソース形式で保存され、コード ジェネレータはアセンブリのリソースからの値を初期化するコードを生成します。

コンポーネントに初期化を認識させる

マネージ コンポーネントを記述する場合、実際的な方法はマネージ コンポーネントをプロパティ セットに対してモードレスに維持することです。 これは、ユーザーがオブジェクト上で設定するプロパティの順序に関係なく同じ結果が得られることを意味します。 たとえば、ユーザーは、コントロール上でテキスト、サイズ、背景色、参照可能範囲の順に設定しても、 背景色、参照可能範囲、サイズ、テキストの順に設定しても同じ結果が得られます。 ただし、プロパティの値が別のプロパティの値に依存していて標準ルールに従う必要がある場合など、少数の例外を除きます。

多くの場合、ユーザーがコーディング時にコンポーネントに対して適用する必要があるプロパティ間の相互関係を、デザイン時に適用することは困難です。 ユーザーが最小値と最大値の間でインジケータを動かして値を選択できるスライダ コントロールを想定します。 また、最小の既定値を 0、最大の既定値を 10 とします。

デザイナでは、ユーザーは最小値に -100、最大値に -1、現在値に -50 を設定します。実行時に、値のプロパティが設定され、値が最小値と最大値の間にないと、コントロールによって例外がスローされます。 ただし、コードが生成されると、プロパティ セットが実行される順序は保証されません。最大値が最小値の前に設定された場合に、初期化コード内から例外がスローされることは好ましくありません。 この問題に対処するために、System.Windows.Forms.TrackBar コントロールを使用します。

この問題を解決するには、クラスに System.ComponentModel.ISupportInitialize インターフェイスを実装します。 このインターフェイスは単純です。 次の 2 つのメソッドを保持しています。 BeginInit と EndInit です。 これらのメソッドは、それぞれ初期化の開始時と終了時に自然に呼び出されます。 コード ジェネレータが ISupportInitialize オブジェクトに対して生成するコードを次に示します。

this.trackBar1 = new System.Windows.Forms.TrackBar();
((System.ComponentModel.ISupportInitialize)
            (this.trackBar1)).BeginInit();
this.trackBar1.Maximum = 50;
this.trackBar1.Minimum = 10;
this.trackBar1.Name = "trackBar1";
this.trackBar1.Value = 25;
((System.ComponentModel.ISupportInitialize)(this.trackBar1)).EndInit();

内部では、TrackBar が初期化時に値を検証しないことを示すフラグを設定します。 EndEdit は呼び出されると、渡された値が有効であることを確認するために設定に関するバッチ検証を実行します。

デザイン時に固有の情報のシリアル化

コンポーネントがデザイン上にあるときに、デザイン時にだけ関係のあるコンポーネントの追加情報を永続化することは有益です。 たとえば、フォームは、フォーム上で調整されるグリッドのサイズを決定する "GridSize" と呼ばれるプロパティを保持しています。 フォーム クラス自体は "GridSize" プロパティを保持していないため、このデータは生成されたコードに保存することはできません。 これに対処するには、"デザイン時属性" を使用します。

通常、デザイン時プロパティはコンポーネント上の実際のプロパティに対応していません。 代わりに、これらのプロパティはデザイナによって追加されます。 これらのプロパティがコード ファイルに生成されないようにするには、System.ComponentModel.DesignOnlyAttribute を適用します。 この属性が設定されたプロパティの値は、コードにシリアル化されるのではなくフォームのデザイン時リソース ファイル (.resx) にシリアル化されます。 デザイナに関する詳細については、.NET コンポーネントのためのカスタム デザイナの作成 を参照してください。

また、添付された ButtonArray のサンプルでは、デザイン時プロパティのコード例が示されています。

internal class ButtonArrayDesigner : ControlDesigner
{
   private bool shadeButtons = true;

   public ButtonArrayDesigner()
   {         
   }

   // The "DesignOnly" attribute will cause this property
   // to be persisted into the resources file rather
   // than the code stream.
//
   [DesignOnly(true), Category("Design"), DefaultValue(true)]
   public bool ShadeButtons 
   {
      get 
      {
         return shadeButtons;
      }
      set 
      {
         if (shadeButtons != value) 
         {
            shadeButtons = value;
            Control.Invalidate();
         }
      }
   }
   protected override void 
      PreFilterProperties(IDictionary properties) 
   {
      // create and add the ShadeButtons property
      //
      PropertyDescriptor shadeProp = 
         TypeDescriptor.CreateProperty(
typeof(ButtonArrayDesigner), 
"ShadeButtons", 
typeof(bool));
      properties[shadeProp.Name] = shadeProp;
      base.PostFilterProperties(properties);
   }
//?E
}

コード生成の制御

初期化時にオブジェクト上で特別なメソッドを呼び出す必要がある場合はどうすればよいでしょうか ? Microsoft Windows® フォーム コントロール コンテナをプロジェクトに追加すると、次のようなメソッドが呼び出されます。

private void InitializeComponent()
{
   this.button1 = new System.Windows.Forms.Button();
   this.SuspendLayout();
   // 
   // button1
   // 
   this.button1.Location = new System.Drawing.Point(128, 96);
   this.button1.Text = "button1";
   // 
   // form1
   // 
   this.Controls.AddRange(new System.Windows.Forms.Control[] {
 this.button1});
   this.Name = "form1";
   this.Text = "Form1";
   this.ResumeLayout(false);
}

フォーム上で "SuspendLayout" 呼び出しと "ResumeLayout" 呼び出しが作成されています。 パネルを子コントロールとともにフォームに追加すると、パネルにも SuspendLayout 呼び出しと対応する ResumeLayout 呼び出しが設定されます。 これによってどのような処理を実行できるようになるのでしょうか ?

Visual Studio .NET デザイナによるコード生成では、最初に "CodeDom Tree" と呼ばれるものが生成されます。 "Code Document Object Model" の省略形である CodeDom は、言語に依存しない方法でコードを表現し、これらのコードは言語固有のコード ジェネレータに渡されます。 これは "抽象構文ツリー" と呼ばれるものの場合とよく似ています。 CodeDom を使用してステートメントのセットをビルドすると、これらは Microsoft Visual Basic® .NET、C#、Microsoft JScript® .NET などの .NET 言語のコード ジェネレータに渡されそれぞれの言語に適したコードが生成されます。 こうして、デザイナは特定の言語に限定されることなく、任意の.NET 言語でコードを生成することができます。 詳細については、.NET Framework ドキュメントおよび MSDN に掲載されている CodeDom の記事を参照してください。

簡単な例として、CodeDom を使用して次の単純な Visual Basic .NET コードを生成する方法を見てみましょう。

Dim i As Integer = 10

If (i = 10) Then
           i = 5
      End If

最初に、ステートメントをビルドする必要があります。 このステートメントは冗長になりますが、いったん理解するとより複雑なステートメント ツリーも比較的容易にビルドできるようになります。

' first build the declaration (Dim I as Integer = 10)
'
Dim statements As CodeStatementCollection = New CodeStatementCollection
Dim variable As CodeVariableDeclarationStatement = 
New CodeVariableDeclarationStatement(
GetType(Integer), "i", New CodePrimitiveExpression(10))
Dim variableRef As CodeVariableReferenceExpression = New 
CodeVariableReferenceExpression("i")
statements.Add(variable)

' now build the assignment inside the if (i = 5)
'
Dim assign As CodeAssignStatement = New CodeAssignStatement
assign.Left = variableRef
assign.Right = New CodePrimitiveExpression(5)

' finally build the if statement
'
Dim ifStatement As CodeConditionStatement = New CodeConditionStatement

' build the condition, which is a binary expression of type '='
'
ifStatement.Condition = New CodeBinaryOperatorExpression(
variableRef, 
CodeBinaryOperatorType.ValueEquality, 
New CodePrimitiveExpression(10))

' add the statements to be executed if the condition results in true
'
ifStatement.TrueStatements.Add(assign)
statements.Add(ifStatement)

式がビルドされたので、次にコードを生成します。

Dim provider As CodeDomProvider
Dim generateVB As Boolean = True

' create the appropriate generator
'
If (generateVB) Then
    provider = New Microsoft.VisualBasic.VBCodeProvider
Else
    provider = New Microsoft.CSharp.CSharpCodeProvider
End If

Dim generator As ICodeGenerator = provider.CreateGenerator()

' now walk the statement collection we created above and 
' output each line of code to the console standard output.
'
For Each s As CodeStatement In statements
     generator.GenerateCodeFromStatement(s, Console.Out, Nothing)
Next s

これで "CodeDom の速習講座" は完了です。コンポーネントに対するコードの生成をカスタマイズする方法に注目します。 各コンポーネント クラスは、System.ComponentModel.Design.Serialization.DesignerSerializer 属性を使用して自分自身を Serializer に関連付けています。 フレームワークでは大半の基本型に対するシリアライザが組み込まれていますが、コンポーネントの作成者がこのプロセスに参加できる方法もあります。 シリアライザを使用すると希望する処理を行えますが、通常、デザイナでは System.ComponentModel.Design.Serialization.CodeDomSerializer に基づいたシリアライザが使用されます。 シリアル化プロセスを変更する場合は、シリアライザをこのクラスから派生します。 ただし、CodeDomSerializer から派生されないシリアライザがあります。 たとえば、デザイナ リソース (.resx ファイル) の値を移動させる役割を果たすシリアライザは CodeDomSerializers ではないため、CodeDom 以外の方法で情報を出力するシリアライザを記述するのが適切です。 ただし、このシリアライザはデザイン時にのみ機能することを覚えておく必要があります。

例として、添付された ButtonArray のサンプルではコード生成に対してコードのビットが 2 箇所に (太字で表示されている部分) 追加されています。 最初の部分では、ButtonArray のコード上で生成されるコメントに動的な情報が追加されています。 次の部分では、ButtonArray のプロパティが生成された後にカスタム メソッドの呼び出しが追加されています。

'This is a custom comment added by a custom serializer on Thursday,
'February 28, 2002
'
'ButtonArray1
'
Me.ButtonArray1.Location = New System.Drawing.Point(64, 32)
Me.ButtonArray1.Name = "ButtonArray1"
Me.ButtonArray1.Size = New System.Drawing.Size(112, 112)
Me.ButtonArray1.CustomMethod()

これらのコードは両方とも CodeDom を使用して生成されました。 最初のステップで、DesignerSerializerAttriubute を使用してカスタム CodeDomSerializer を ButtonArray クラスに関連付けています。

[DesignerSerializer(typeof(ButtonArraySerializer), 
   typeof(CodeDomSerializer))]
public class ButtonArray : System.Windows.Forms.UserControl, 
                     ISupportInitialize { //?E
}

次のステップで、独自のシリアライザを実装しています。 添付されたサンプルの完全なコードでは、常に "基本" シリアライザをシリアル化マネージャに要求し、既定のシリアル化のためにこれを呼び出していることに注意する必要があります。 これは重要なステップです。このステップがないとコードは適切にシリアル化されません。 また、単純なコードだけを生成しているので、基本 CodeDomSerializer によって逆シリアル化が処理されるため、Serialize メソッドの作業だけを行っています。

/// <summary>
/// The custom serializer for the ButtonArray type that adds
/// some simple, sample output to the code that is generated for the
/// ButtonArray
/// </summary>
public class ButtonArraySerializer : CodeDomSerializer
{

      // See sample for the Deserialize method

/// <summary>
/// We customize the output from the default serializer here, adding
/// a comment and an extra line of code.
/// </summary>
public override object Serialize(
IDesignerSerializationManager manager, 
object value) 
{
// first, locate and invoke the default serializer for 
// the ButtonArray's  base class (UserControl)
//
CodeDomSerializer baseSerializer = 
(CodeDomSerializer)manager.GetSerializer(
typeof(ButtonArray).BaseType, 
typeof(CodeDomSerializer));

   object codeObject = baseSerializer.Serialize(manager, value);

   // now add some custom code
   //
   if (codeObject is CodeStatementCollection) 
   {

      // add a custom comment to the code.
      //
      CodeStatementCollection statements = 
(CodeStatementCollection)codeObject;   
      statements.Insert(0, 
new CodeCommentStatement(
"This is a custom comment added by a custom serializer on " + 
               DateTime.Now.ToLongDateString()));

      // call a custom method.
      //
      CodeExpression targetObject = 
         base.SerializeToReferenceExpression(manager, value);
         if (targetObject != null) 
         {
            CodeMethodInvokeExpression methodCall = 
new CodeMethodInvokeExpression(targetObject, "CustomMethod");
            statements.Add(methodCall);
         }
         
      }

      // finally, return the statements that have been created
      return codeObject;
   }
}

このシリアライザを実行すると、上記のサンプル コード、または同等の C# のコードが生成されます。 この方法は、Windows フォーム コンテナ コントロールを使用して SupendLayout と ResumeLayout の関数呼び出しをコード生成に追加することによって実現できます。

まとめ

Visual Studio .NET における .NET Framework Visual Designers は、強力かつ柔軟なコード生成機能が組み込まれています。コード生成では、コンポーネントの作成者の負担なしに大半のコンポーネントの永続性が適切に処理されます。 さらに詳細なシナリオや特定のニーズに応えるために、コード生成の直接的な拡張機能が公開され、既定の処理では各ユーザーのニーズを満たすことができないシナリオをカバーしています。