デザイナのシリアル化の概要

更新 : 2007 年 11 月

デザイナのシリアル化を使用すると、デザイン時または実行時のコンポーネントの状態を保持できます。

オブジェクトのシリアル化

.NET Framework では、コード生成、SOAP シリアル化、バイナリ シリアル化、XML シリアル化など、さまざまな種類のシリアル化をサポートしています。

デザイナのシリアル化は特殊な形式のシリアル化で、通常、開発ツールに関連付けられているオブジェクトの永続化の種類に対応しています。デザイナのシリアル化は、オブジェクト グラフをソース ファイルに変換して、後でオブジェクト グラフの復元に使用できるようにするプロセスです。ソース ファイルには、コード、マークアップ、または SQL テーブル情報も含めることができます。デザイナのシリアル化は、すべての共通言語ランタイム オブジェクトで使用できます。

デザイナのシリアル化は、一般的なオブジェクトのシリアル化と次の点で異なります。

  • シリアル化を実行するオブジェクトは、ランタイム オブジェクトとは別です。そのため、そのデザイン時のロジックはコンポーネントから削除できます。

  • シリアル化のスキームは、オブジェクトが完全に初期化された状態で作成され、逆シリアル化の際にプロパティおよびメソッドの呼び出しによって変更されることを前提にしています。

  • オブジェクトのプロパティは、その値がオブジェクトに設定されていない場合、シリアル化されません。逆に言うと、一部のプロパティ値は逆シリアル化ストリームで初期化されないことがあります。シリアル化のルールの詳細については、このトピックで後述する「一般的なシリアル化のルール」を参照してください。

  • 重要なのは、オブジェクトの完全なシリアル化ではなく、シリアル化ストリーム内のコンテンツの質です。オブジェクトをシリアル化する方法が定義されていない場合は、例外が発生するのではなく、そのオブジェクトが渡されます。デザイナのシリアル化には、あいまいでわかりにくい形式ではなく、簡単なわかりやすい形式でオブジェクトをシリアル化する方法が用意されています。

  • シリアル化ストリームには、逆シリアル化に必要とされるよりも多くのデータが含まれる場合があります。たとえば、ソース コードをシリアル化すると、オブジェクト グラフの逆シリアル化に必要なコードがユーザー コードに混入します。このユーザー コードはシリアル化の際に保持され、逆シリアル化の際に渡される必要があります。

ms171834.alert_note(ja-jp,VS.90).gifメモ :

デザイナのシリアル化は、デザイン時だけでなく実行時にも使用できます。

.NET Framework デザイナのシリアル化インフラストラクチャによって達成されるデザインの目標を次の表に示します。

デザインの目標

説明

モジュール

シリアル化プロセスを拡張して新しいデータ型を提供できます。これらのデータ型は、それ自体についての便利でわかりやすい説明として扱うことができます。

簡単に拡張可能

シリアル化プロセスを拡張して、簡単に新しいデータ型を提供できます。

中立的な形式

オブジェクトは多くの異なるファイル形式で使用できるため、デザイナのシリアル化は特定のデータ形式に縛られません。

ATL Server アーキテクチャ

デザイナのシリアル化のアーキテクチャは、メタデータ、シリアライザ、およびシリアル化マネージャによって決まります。アーキテクチャの各側面の役割について、次の表で説明します。

側面

説明

メタデータの属性

属性は、型 T をいくつかのシリアライザ S に関連付けるために使用されます。また、アーキテクチャは、"bootstrapping" 属性をサポートしています。この属性を使用して、シリアライザを持たない型にシリアライザを提供できるオブジェクトをインストールできます。

シリアライザ

シリアライザは、特定の型または型の範囲をシリアル化できるオブジェクトです。各データ形式に対して、1 つの基本クラスがあります。たとえば、オブジェクトを XML に変換できる DemoXmlSerializer 基本クラスがあります。アーキテクチャは特定のシリアル化形式に依存しません。また、Code Document Object Model (CodeDOM) に基づいて構築されたこのアーキテクチャの実装も含まれます。

シリアル化マネージャ

シリアル化マネージャは、オブジェクト グラフのシリアル化に使用されるすべてのシリアライザの情報ストアを提供するオブジェクトです。50 のオブジェクトから成るグラフには、50 の異なるシリアライザが含まれ、それらはすべて固有の出力を生成します。シリアル化マネージャは、これらのシリアライザが互いに通信するために使用されます。

次の図および手順に、グラフ内のオブジェクト (ここでは A および B) をシリアル化する方法を示します。

オブジェクト グラフのシリアル化

グラフ内のオブジェクトのシリアル化

  1. 呼び出し元は、シリアル化マネージャからオブジェクト A のシリアライザを要求します。

    MySerializer s = manager.GetSerializer(a);
    
  2. A の型のメタデータ属性が、要求された型のシリアライザにバインドされます。次に、呼び出し元は、シリアライザに対して A のシリアル化を要求します。

    Blob b = s.Serialize(manager, a);
    
  3. オブジェクト A のシリアライザは A をシリアル化します。A のシリアル化の際に検出したオブジェクトごとに、シリアル化マネージャから追加のシリアライザを要求します。

    MySerializer s2 = manager.GetSerializer(b);
    Blob b2 = s2.Serialize(manager, b);
    
  4. シリアル化の結果が呼び出し元に返されます。

    Blob b = ...
    

一般的なシリアル化のルール

通常、コンポーネントはプロパティの数を公開します。たとえば、Windows フォームの Button コントロールには、BackColorForeColor、および BackgroundImage などのプロパティがあります。Button コントロールをデザイナのフォームに配置し、生成されたコードを表示すると、プロパティのサブセットのみがコード内に保持されていることがわかります。通常、これらは明示的に値が設定されたプロパティです。

Button コントロールに関連付けられている CodeDomSerializer は、シリアル化の動作を定義します。CodeDomSerializer がプロパティの値をシリアル化するときに使用するルールの一部を、次の一覧で説明します。

  • プロパティに DesignerSerializationVisibilityAttribute がアタッチされている場合、シリアライザはこれを使用してプロパティがシリアル化されているかどうか (Visible または Hidden)、およびシリアル化の方法 (Content) を判断します。

  • Visible または Hidden 値は、プロパティがシリアル化されているかどうかを示します。Content 値は、プロパティのシリアル化の方法を示します。

  • DemoProperty というプロパティの場合、コンポーネントが ShouldSerializeDemoProperty というメソッドを実装すると、デザイナ環境は遅延バインディングによってこのメソッドを呼び出して、シリアル化するかどうかを判断します。たとえば、BackColor プロパティの場合、メソッドの名前は ShouldSerializeBackColor です。

  • プロパティに DefaultValueAttribute が指定されている場合は、既定値がコンポーネントの現在の値と比較されます。現在の値が既定値でない場合にのみ、プロパティはシリアル化されます。

  • コンポーネントに関連付けられているデザイナは、プロパティのシャドウまたは ShouldSerialize メソッド自体の実装により、シリアル化するかどうかの決定にも関与します。

ms171834.alert_note(ja-jp,VS.90).gifメモ :

シリアライザは、シリアル化するかどうかをプロパティに関連付けられている PropertyDescriptor に従って決定し、PropertyDescriptor は前述のルールを使用します。

コンポーネントを別の方法でシリアル化する場合は、CodeDomSerializer から派生したシリアライザ クラスを独自に記述し、DesignerSerializerAttribute を使用してそれをコンポーネントに関連付けます。

スマート シリアライザの実装

シリアライザ デザインには、新しいシリアル化形式が必要な場合は、メタデータ属性ですべてのデータ型を更新してその形式をサポートする必要があるという要件があります。ただし、汎用オブジェクト メタデータを使用するシリアライザにシリアル化プロバイダを組み合わせて使用することにより、この要件を満たすことができます。ここでは、特定の形式のシリアライザをデザインすることにより、多数のカスタム シリアライザが必要になる状況を最小限に抑える推奨される方法について説明します。

次のスキーマは、オブジェクト グラフが保持される仮想 XML 形式を定義します。

<TypeName>
    <PropertyName>
        ValueString
    </PropertyName>
</TypeName>

この形式は、DemoXmlSerializer と呼ばれる開発されたクラスを使用してシリアル化されます。

public abstract class DemoXmlSerializer 
{
    public abstract string Serialize(
        IDesignerSerializationManager m, 
        object graph);
}

DemoXmlSerializer が文字列を断片から構築するモジュール式のクラスであることを理解する必要があります。たとえば、Int32 データ型の DemoXmlSerializer は、値 23 の整数を渡されたとき、文字列 "23" を返します。

シリアル化プロバイダ

前述のスキーマの例で説明したとおり、処理する基本的な型には次の 2 つがあります。

  • 子プロパティを持つオブジェクト。

  • テキストに変換できるオブジェクト。

すべてのクラスを、そのクラスをテキストまたは XML タグに変換できるカスタム シリアライザで扱うことは困難です。シリアル化プロバイダは、コールバック機構を提供することによってこれを解決します。この機構により、オブジェクトは指定された型に対してシリアライザを提供できます。この例では、使用可能な型のセットは、次の条件によって制限されています。

  • IConvertible インターフェイスを使用して型を文字列に変換できる場合は、StringXmlSerializer が使用されます。

  • 型を文字列に変更できない場合で、その型がパブリックであり、空のコンストラクタを持つときは、ObjectXmlSerializer が使用されます。

  • これらがいずれも当てはまらない場合、シリアル化プロバイダは null を返して、オブジェクトにシリアライザがないことを示します。

呼び出し元シリアライザがこのエラーの発生によって生じた問題を解決する方法を次のコード例に示します。

internal class XmlSerializationProvider : IDesignerSerializationProvider 
{
    object GetSerializer(
        IDesignerSerializationManager manager, 
        object currentSerializer, 
        Type objectType, 
        Type serializerType) 
    {

        // Null values will be given a null type by this serializer.
        // This test handles this case.
        if (objectType == null) 
        {
            return StringXmlSerializer.Instance;
        }

        if (typeof(IConvertible).IsSubclassOf(objectType)) 
        {
            return StringXmlSerializer.Instance;
        }

        if (objectType.GetConstructor(new object[]) != null) 
        {
            return ObjectXmlSerializer.Instance;
        }

        return null;
    }
}

シリアル化プロバイダを定義したら、使用するために配置する必要があります。AddSerializationProvider メソッドにより、シリアル化マネージャにシリアル化プロバイダを追加できます。ただし、そのためには、この呼び出しをシリアル化マネージャに対して行う必要があります。DefaultSerializationProviderAttribute をシリアライザに追加すると、シリアル化プロバイダがシリアル化マネージャに自動的に追加されます。この属性では、シリアル化プロバイダにパブリックな空のコンストラクタが必要です。DemoXmlSerializer に加える必要がある変更を次のコード例に示します。

[DefaultSerializationProvider(typeof(XmlSerializationProvider))]
public abstract class DemoXmlSerializer 
{
}

これにより、シリアル化マネージャがいずれかの型の DemoXmlSerializer を要求されると、既定のシリアル化プロバイダがシリアル化マネージャに追加されます (まだ追加されていない場合)。

シリアライザ

サンプル DemoXmlSerializer クラスには、StringXmlSerializer と ObjectXmlSerializer という名前の 2 つの具象シリアライザ クラスがあります。StringXmlSerializer の実装例を次のコード例に示します。

internal class StringXmlSerializer : DemoXmlSerializer
{
    internal StringXmlSerializer Instance = new StringXmlSerializer();

    public override string Serialize(
        IDesignerSerializationManager m, 
        object graph) 
    {

        if (graph == null) return string.Empty;

        IConvertible c = graph as IConvertible;
        if (c == null) 
        {
            // Rather than throwing exceptions, add a list of errors 
            // to the serialization manager.
            m.ReportError("Object is not IConvertible");
            return null;
        }

    return c.ToString(CultureInfo.InvariantCulture);
    }
}

ObjectXmlSerializer の実装は、オブジェクトのパブリック プロパティを列挙する必要があるため、さらに複雑です。ObjectXmlSerializer の実装例を次のコード例に示します。

internal class ObjectXmlSerializer : DemoXmlSerializer 
{
    internal ObjectXmlSerializer Instance = new ObjectXmlSerializer();

    public override string Serialize(
        IDesignerSerializationManager m, 
        object graph) 
    {

        StringBuilder xml = new StringBuilder();
        xml.Append(“<”);
        xml.Append(graph.GetType().FullName);
        xml.Append(“>”);

        // Now, walk all the properties of the object.
        PropertyDescriptorCollection properties;
        Property p;

        properties = TypeDescriptor.GetProperties(graph);

        foreach(p in properties) 
        {
            if (!p.ShouldSerializeValue(graph)) 
            {
                continue;
            }

            object value = p.GetValue(graph);
            Type valueType = null;
            if (value != null) valueType = value.GetType();

            // Get the serializer for this property
            DemoXmlSerializer s = m.GetSerializer(
                valueType, 
                typeof(DemoXmlSerializer)) as DemoXmlSerializer;

            if (s == null) 
            {
                // Because there is no serializer, 
                // this property must be passed over.  
                // Tell the serialization manager
                // of the error.
                m.ReportError(string.Format(
                    “Property {0} does not support XML serialization”,
                    p.Name));
                continue;
            }

            // You have a valid property to write.
            xml.Append(“<”);
            xml.Append(p.Name);
            xml.Append(“>”);

            xml.Append(s.Serialize(m, value);

            xml.Append(“</”);
            xml.Append(p.Name);
            xml.Append(“>”);
        }

        xml.Append(“</”);
        xml.Append(graph.GetType().FullName);
        xml.Append(“>”);
        return xml.ToString();
    }
}

ObjectXmlSerializer は、プロパティ値ごとに他のシリアライザを呼び出します。これには 2 つの利点があります。まず、ObjectXmlSerializer を非常に簡略化できます。次に、これを利用してサードパーティの型を拡張できます。いずれのシリアライザを使用しても記述できない型に ObjectXmlSerializer が遭遇した場合は、その型にカスタム シリアライザを提供できます。

使用法

これらの新しいシリアライザを使用するには、IDesignerSerializationManager のインスタンスを作成する必要があります。このインスタンスからシリアライザを要求し、シリアライザに対してオブジェクトのシリアル化を要求します。次のコード例では、シリアル化するオブジェクトに Rectangle を使用します。これは、この型には空のコンストラクタがあり、IConvertible をサポートする 4 つのプロパティがあるからです。IDesignerSerializationManager を自分で実装する代わりに、DesignerSerializationManager によって提供されている実装を使用できます。

Rectangle r = new Rectangle(5, 10, 15, 20);
DesignerSerializationManager m = new DesignerSerializationManager();
DemoXmlSerializer x = (DemoXmlSerializer)m.GetSerializer(
    r.GetType(), typeof(DemoXmlSerializer);

string xml = x.Serialize(m, r);

これにより、次の XML が作成されます。

<System.Drawing.Rectangle>
<X>
5
</X>
<Y>
10
</Y>
<Width>
15
</Width>
<Height>
15
</Height>
</System.Drawing.Rectangle>
ms171834.alert_note(ja-jp,VS.90).gifメモ :

XML にはインデントが設定されていません。これは IDesignerSerializationManagerContext プロパティで簡単に実現できます。シリアライザの各レベルでは、現在のインデント レベルを含むコンテキスト スタックにオブジェクトを追加できます。また、各シリアライザは、そのオブジェクトをスタック内で検索し、それを使用してインデントを提供できます。

参照

参照

IDesignerSerializationManager

DesignerSerializationManager

DefaultSerializationProviderAttribute

IConvertible

CodeDomSerializerBase

その他の技術情報

デザイン時サポートの拡張