デザイナーのシリアル化の概要
デザイナーのシリアル化を使用すると、デザイン時または実行時のコンポーネントの状態を保持できます。
オブジェクトのシリアル化
.NET Framework では、コード生成、SOAP シリアル化、バイナリ シリアル化、XML シリアル化など、さまざまな種類のシリアル化をサポートしています。
デザイナーのシリアル化は特殊な形式のシリアル化で、通常、開発ツールに関連付けられているオブジェクトの永続化の種類に対応しています。デザイナー シリアル化は、オブジェクト グラフをソース ファイルに変換するプロセスです。後でそのソース ファイルを使用して、オブジェクト グラフを復元できます。ソース ファイルには、コードやマークアップ、または SQL テーブル情報が含まれることもあります。デザイナーのシリアル化は、すべての共通言語ランタイム オブジェクトで使用できます。
デザイナーのシリアル化は、一般的なオブジェクトのシリアル化と次の点で異なります。
シリアル化を実行するオブジェクトは、ランタイム オブジェクトとは別です。そのため、そのデザイン時のロジックはコンポーネントから削除できます。
シリアル化のスキームは、オブジェクトが完全に初期化された状態で作成され、逆シリアル化の際にプロパティおよびメソッドの呼び出しによって変更されることを前提にしています。
オブジェクトのプロパティは、その値がオブジェクトに設定されていない場合、シリアル化されません。逆に言うと、一部のプロパティ値は逆シリアル化ストリームで初期化されないことがあります。シリアル化のルールの詳細については、このトピックで後述する「一般的なシリアル化のルール」を参照してください。
重要なのは、オブジェクトの完全なシリアル化ではなく、シリアル化ストリーム内のコンテンツの質です。オブジェクトをシリアル化する方法が定義されていない場合は、例外が発生するのではなく、そのオブジェクトが渡されます。デザイナーのシリアル化には、あいまいでわかりにくい形式ではなく、簡単なわかりやすい形式でオブジェクトをシリアル化する方法が用意されています。
シリアル化ストリームには、逆シリアル化に必要とされるよりも多くのデータが含まれる場合があります。たとえば、ソース コードをシリアル化すると、オブジェクト グラフの逆シリアル化に必要なコードがユーザー コードに混入します。このユーザー コードはシリアル化の際に保持され、逆シリアル化の際に渡される必要があります。
[!メモ]
デザイナーのシリアル化は、デザイン時だけでなく実行時にも使用できます。
.NET Framework デザイナーのシリアル化インフラストラクチャによって達成されるデザインの目標を次の表に示します。
デザインの目標 |
Description |
---|---|
モジュール |
シリアル化プロセスを拡張して新しいデータ型を提供できます。これらのデータ型は、それ自体についての便利でわかりやすい説明として扱うことができます。 |
簡単に拡張可能 |
シリアル化プロセスを拡張して、簡単に新しいデータ型を提供できます。 |
中立的な形式 |
オブジェクトは多くの異なるファイル形式で使用できるため、デザイナーのシリアル化は特定のデータ形式に縛られません。 |
アーキテクチャ
デザイナーのシリアル化のアーキテクチャは、メタデータ、シリアライザー、およびシリアル化マネージャーによって決まります。アーキテクチャの各側面の役割について、次の表で説明します。
側面 |
Description |
---|---|
メタデータの属性 |
属性は、型 T をいくつかのシリアライザー S に関連付けるために使用されます。また、アーキテクチャは、"bootstrapping" 属性をサポートしています。この属性を使用して、シリアライザーを持たない型にシリアライザーを提供できるオブジェクトをインストールできます。 |
シリアライザー |
シリアライザーは、特定の型または型の範囲をシリアル化できるオブジェクトです。各データ形式に対して、1 つの基本クラスがあります。たとえば、オブジェクトを XML に変換できる DemoXmlSerializer 基本クラスがあります。アーキテクチャは特定のシリアル化形式に依存しません。また、Code Document Object Model (CodeDOM) に基づいて構築されたこのアーキテクチャの実装も含まれます。 |
シリアル化マネージャー |
シリアル化マネージャーは、オブジェクト グラフのシリアル化に使用されるすべてのシリアライザーの情報ストアを提供するオブジェクトです。50 のオブジェクトから成るグラフには、50 の異なるシリアライザーが含まれ、それらはすべて固有の出力を生成します。シリアル化マネージャーは、これらのシリアライザーが互いに通信するために使用されます。 |
次の図および手順に、グラフ内のオブジェクト (ここでは A および B) をシリアル化する方法を示します。
グラフ内のオブジェクトのシリアル化
呼び出し元は、シリアル化マネージャーからオブジェクト A のシリアライザーを要求します。
MySerializer s = manager.GetSerializer(a);
A の型のメタデータ属性が、要求された型のシリアライザーにバインドされます。次に、呼び出し元は、シリアライザーに対して A のシリアル化を要求します。
Blob b = s.Serialize(manager, a);
オブジェクト A のシリアライザーは A をシリアル化します。A のシリアル化の際に検出したオブジェクトごとに、シリアル化マネージャーから追加のシリアライザーを要求します。
MySerializer s2 = manager.GetSerializer(b); Blob b2 = s2.Serialize(manager, b);
シリアル化の結果が呼び出し元に返されます。
Blob b = ...
一般的なシリアル化のルール
通常、コンポーネントはプロパティの数を公開します。たとえば、Windows フォームの Button コントロールには、BackColor、ForeColor、および BackgroundImage などのプロパティがあります。Button コントロールをデザイナーのフォームに配置し、生成されたコードを表示すると、プロパティのサブセットのみがコード内に保持されていることがわかります。通常、これらは明示的に値が設定されたプロパティです。
Button コントロールに関連付けられている CodeDomSerializer は、シリアル化の動作を定義します。CodeDomSerializer がプロパティの値をシリアル化するときに使用するルールの一部を、次の一覧で説明します。
プロパティに DesignerSerializationVisibilityAttribute がアタッチされている場合、シリアライザーはこれを使用してプロパティがシリアル化されているかどうか (Visible または Hidden)、およびシリアル化の方法 (Content) を判断します。
Visible または Hidden 値は、プロパティがシリアル化されているかどうかを示します。Content 値は、プロパティのシリアル化の方法を示します。
DemoProperty というプロパティの場合、コンポーネントが ShouldSerializeDemoProperty というメソッドを実装すると、デザイナー環境は遅延バインディングによってこのメソッドを呼び出して、シリアル化するかどうかを判断します。たとえば、BackColor プロパティの場合、メソッドの名前は ShouldSerializeBackColor です。
プロパティに DefaultValueAttribute が指定されている場合は、既定値がコンポーネントの現在の値と比較されます。現在の値が既定値でない場合にのみ、プロパティはシリアル化されます。
コンポーネントに関連付けられているデザイナーは、プロパティのシャドウまたは ShouldSerialize メソッド自体の実装により、シリアル化するかどうかの決定にも関与します。
[!メモ]
シリアライザーは、シリアル化するかどうかをプロパティに関連付けられている 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>
[!メモ]
XML にはインデントが設定されていません。これは IDesignerSerializationManager の Context プロパティで簡単に実現できます。シリアライザーの各レベルでは、現在のインデント レベルを含むコンテキスト スタックにオブジェクトを追加できます。また、各シリアライザーは、そのオブジェクトをスタック内で検索し、それを使用してインデントを提供できます。
参照
関連項目
DefaultSerializationProviderAttribute