設計工具序列化概觀
您可以利用設計工具序列化,在設計階段或執行階段保存元件的狀態。
物件的序列化
.NET Framework 支援幾種序列化作業,例如程式碼產生、SOAP 序列化、二進位序列化,以及 XML 序列化。
「設計工具序列化」是特殊形式的序列化,其中牽涉到通常是與部署工具相關聯的物件持續性。 設計工具序列化為將物件 Graph 轉換為原始程式檔 (Source File) 的程序,稍後可以用於復原物件 Graph。 原始程式檔可以包含程式碼、標記,甚至是 SQL 資料表資訊。 設計工具序列化適用於所有 Common Language Runtime 物件。
設計工具序列化與一般物件序列化不同,其差異包括下列幾個方面:
執行序列化的物件與執行階段物件分開,使得設計階段邏輯能夠從元件移除。
序列化機制的設計假設是:物件將在完整初始化的狀態中建立,然後在還原序列化 (Deserialization) 過程中,透過屬性和方法引動進行修改。
物件若有從未在其上設定的值,則此物件的屬性不進行序列化。 反過來說,還原序列化資料流也不可以初始化所有屬性值。 如需序列化規則的詳細描述,請參閱本主題下文中的<一般序列化規則>一節。
強調重點是序列化資料流中內容的品質,而不是物件的完整序列化。 如果沒有定義序列化物件的方式,則會傳遞該物件,而不引發例外狀況。 設計工具序列化有多種序列化物件的方式,是以簡單而人們可讀取的 (Human-Readable) 的形式進行,而不是以不透明 BLOB 的形式進行。
序列化資料流可能會有超過還原序列化所需的資料。 例如,原始程式碼序列化會有使用者程式碼混合了將物件圖形還原序列化所需的程式碼。 這套使用者程式碼必須在序列化時保存,而在還原序列化時略過不用。
注意事項 |
---|
執行階段和設計階段都可以使用設計工具序列化。 |
下表將列出利用 .NET Framework 設計工具序列化基礎結構所完成的設計目標。
設計目標 |
描述 |
---|---|
模組化 |
序列化處理序可擴充以涵蓋新的資料型別,並且這些資料型別可以提供有用而人們可讀取的資料本身描述。 |
很容易擴充 |
序列化處理序很容易就能擴充,以涵蓋新的資料型別。 |
格式中性 |
物件可以參與多種不同的檔案格式,設計工具序列化也並未繫結至特定的資料格式。 |
架構
設計工具序列化架構是以中繼資料、序列化程式和序列化管理員為基礎。 下表說明架構各方面的角色。
方面 |
描述 |
---|---|
中繼資料屬性 |
屬性是用來讓型別 T 與一些序列化程式 S 產生關聯。 同時,架構也支援「啟動載入」屬性,可用來安裝能為沒有序列化程式的型別提供序列化程式的物件。 |
序列化程式 |
序列化程式是可序列化特定型別或型別範圍的物件。 每一種資料格式都有基底類別。 例如,可能會有 DemoXmlSerializer 基底類別,可將物件轉換成 XML。 架構與任何特定序列化格式都無關,它也包括在「程式碼文件物件模型」(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 Form Button 控制項具有如 BackColor、ForeColor 和 BackgroundImage 等屬性。 當您將 Button 控制項放置在設計工具中的表單上並檢視所產生的程式碼時,會發現程式碼中只保存了屬性的子集。 一般來說,這些都是您已明確設定了值的屬性。
與 Button 控制項相關聯的 CodeDomSerializer 會定義序列化行為。 下列清單說明由 CodeDomSerializer 用來序列化屬性值的一些規則:
如果屬性附加了 DesignerSerializationVisibilityAttribute,序列化程式就會用它來判斷屬性是否已序列化 (如 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"。
序列化提供者
上述機制範例清楚地顯示出要處理兩種基本的型別:
有子屬性的物件
可以轉換成文字的物件
要用可將該類別轉換成文字或 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。 下列程式碼範例會示範 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 叫用各個屬性值的其他序列化程式。 這有兩種好處。 第一,它可以讓 ObjectXmlSerializer 非常簡單。 第二,它會為協力廠商型別提供擴充性點。 如果 ObjectXmlSerializer 是以無法透過這兩種序列化程式寫出的型別呈現,則可為該型別提供自訂序列化程式。
使用方式
若要使用這些新的序列化程式,則必須建立 IDesignerSerializationManager 的執行個體。 從這個執行個體要求序列化程式,然後再要求序列化程式將您的物件序列化。 在下列程式碼範例中,要序列化的物件將使用 Rectangle,因為這個型別有空白的建構函式,而且有四個支援 IConvertible 的屬性。 您不需要自己實作 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