序列化又稱串行化,是.NET運行時環境用來支持用戶定義類型的流化的機制。其目的是以某種存儲形成使自定義對象持久化,或者將這種對象從一個地方傳輸到另一個地方。
.NET框架提供了兩種串行化的方式:1、是使用BinaryFormatter進行串行化;2、使用SoapFormatter進行串行化;3、使用XmlSerializer進行串行化。第一種方式提供了一個簡單的二進制數據流以及某些附加的類型信息,而第二種將數據流格式化為XML存儲;第三種其實和第二種差不多也是XML的格式存儲,只不過比第二種的XML格式要簡化很多(去掉了SOAP特有的額外信息)。
可以使用[Serializable]屬性將類標志為可序列化的。如果某個類的元素不想被序列化,1、2可以使用[NonSerialized]屬性來標志,2、可以使用[XmlIgnore]來標志。
二進制序列器:
對象序列化之后是二進制形式的,通過BinaryFormatter類來實現的,這個類位於System.Runtime.Serialization.Formatters.Binary命名空間下
[Serializable] //使對象可序列化(必須添加)
特性
程序集,類,方法,屬性都可以使用特性
BinaryFormatter //創建二進制序列化器
Serialize(Stream(流),object(序列化對象))
流:可以理解成打通內存和硬盤的一個工具
輸入流:從硬盤到內存
輸出流:從內存到硬盤
XML序列化器:
對象序列化之后的結果符合SOAP協議,也就是可以通過SOAP?協議傳輸,通過System.Runtime.Serialization.Formatters.Soap命名空間下的SoapFormatter類來實現的。
SOAP序列化器:
對象序列化之后的結果是XML形式的,通過XmlSerializer?類來實現的,這個類位於System.Xml.Serialization命名空間下。XML序列化不能序列化私有數據。
反序列化:
將流轉換為對象
Disk(硬盤)--->Cache(內存)
BinaryFormatter //創建二進制序列化器
Deserialize(Stream(流))//返回object類型
1、使用BinaryFormatter進行串行化
我使用的例子是 System.Windows.Forms.TreeNode類;
[System.ComponentModel.TypeConverter(typeof(System.Windows.Forms.TreeNodeConverter))] [System.Serializable] public class TreeNode : MarshalByRefObject, ICloneable, System.Runtime.Serialization.ISerializable
然后我們對這個TreeNode進行 序列化 和 反序列化。
我們先定義控件變量
treevMainForm : 存放TreeNode節點控件
// 序列化
public void SerializeNow() { TreeNodeCollection c = treevMainFomr.Nodes; FileStream fileStream = new FileStream("c:\\temp.dat", FileMode.Create); BinaryFormatter b = new BinaryFormatter(); b.Serialize(fileStream, c); fileStream.Close(); }
// 反序列化 public void DeSerializeNow() { TreeNodeCollection c = new TreeNodeCollection(); FileStream fileStream = new FileStream("c:\\temp.dat", FileMode.Open, FileAccess.Read, FileShare.Read); BinaryFormatter b = new BinaryFormatter(); c = b.Deserialize(fileStream) as TreeNodeCollection; fileStream.Close(); }
2、使用SoapFormatter進行串行化
和BinaryFormatter類似,我們只需要做一下簡單修改即可:
a.將using語句中的.Formatter.Binary改為.Formatter.Soap;
b.將所有的BinaryFormatter替換為SoapFormatter.
c.確保報存文件的擴展名為.xml
經過上面簡單改動,即可實現SoapFormatter的串行化,這時候產生的文件就是一個xml格式的文件。
3、使用XmlSerializer進行串行化
關於格式化器還有一個問題,假設我們需要XML,但是不想要SOAP特有的額外信息,那么我們應該怎么辦呢?有兩中方案:要么編寫一個實現IFormatter接口的類,采用的方式類似於SoapFormatter類,但是沒有你不需要的信息;要么使用庫類XmlSerializer,這個類不使用Serializable屬性,但是它提供了類似的功能。
如果我們不想使用主流的串行化機制,而想使用XmlSeralizer進行串行化我們需要做一下修改:
a.添加System.Xml.Serialization命名空間。
b.Serializable和NoSerialized屬性將被忽略,而是使用XmlIgnore屬性,它的行為與NoSerialized類似。
c.XmlSeralizer要求類有個默認的構造器,這個條件可能已經滿足了。
下面看示例:
要序列化的類:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Xml.Serialization; [Serializable] public class Person { private string name; public string Name { get { return name; } set { name = value; } } public string Sex; public int Age = 31; public Course[] Courses; public Person() { } public Person(string Name) { name = Name; Sex = "男"; } } [Serializable] public class Course { public string Name; [XmlIgnore] public string Description; public Course() { } public Course(string name, string description) { Name = name; Description = description; } }
序列化和反序列化方法:
public void XMLSerialize() { Person c = new Person("cyj"); c.Courses = new Course[2]; c.Courses[0] = new Course("英語", "交流工具"); c.Courses[1] = new Course("數學","自然科學"); XmlSerializer xs = new XmlSerializer(typeof(Person)); Stream stream = new FileStream("c:\\cyj.XML",FileMode.Create,FileAccess.Write,FileShare.Read); xs.Serialize(stream,c); stream.Close(); } public void XMLDeserialize() { XmlSerializer xs = new XmlSerializer(typeof(Person)); Stream stream = new FileStream("C:\\cyj.XML",FileMode.Open,FileAccess.Read,FileShare.Read); Person p = xs.Deserialize(stream) as Person; Response.Write(p.Name); Response.Write(p.Age.ToString()); Response.Write(p.Courses[0].Name); Response.Write(p.Courses[0].Description); Response.Write(p.Courses[1].Name); Response.Write(p.Courses[1].Description); stream.Close(); }
這里Course類的Description屬性值將始終為null,生成的xml文檔中也沒有該節點,如下圖:
<?xml version="1.0"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Sex>男</Sex> <Age>31</Age> <Courses> <Course> <Name>英語</Name> <Description>交流工具</Description> </Course> <Course> <Name>數學</Name> <Description>自然科學</Description> </Course> </Courses> <Name>cyj</Name> </Person>
4、自定義序列化
如果你希望讓用戶對類進行串行化,但是對數據流的組織方式不完全滿意,那么可以通過在自定義類中實現接口來自定義串行化行為。這個接口只有一個方法,GetObjectData. 這個方法用於將對類對象進行串行化所需要的數據填進SerializationInfo對象。你使用的格式化器將構造SerializationInfo對象,然后在串行化時調用GetObjectData. 如果類的父類也實現了ISerializable,那么應該調用GetObjectData的父類實現。
如果你實現了ISerializable,那么還必須提供一個具有特定原型的構造器,這個構造器的參數列表必須與GetObjectData相同。這個構造器應該被聲明為私有的或受保護的,以防止粗心的開發人員直接使用它。
示例如下:
實現ISerializable的類:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; /// <summary> /// Employee 的摘要說明 /// </summary> [Serializable] public class Employee:ISerializable { public int EmpId=100; public string EmpName="劉德華"; [NonSerialized] public string NoSerialString = "NoSerialString-Test"; public Employee() { // // TODO: 在此處添加構造函數邏輯 // } private Employee(SerializationInfo info, StreamingContext ctxt) { EmpId = (int)info.GetValue("EmployeeId", typeof(int)); EmpName = (String)info.GetValue("EmployeeName",typeof(string)); //NoSerialString = (String)info.GetValue("EmployeeString",typeof(string)); } public void GetObjectData(SerializationInfo info, StreamingContext ctxt) { info.AddValue("EmployeeId", EmpId); info.AddValue("EmployeeName", EmpName); //info.AddValue("EmployeeString", NoSerialString); } }
序列化和反序列化方法:
public void OtherEmployeeClassTest() { Employee mp = new Employee(); mp.EmpId = 10; mp.EmpName = "邱楓"; mp.NoSerialString = "你好呀"; Stream steam = File.Open("c:\\temp3.dat", FileMode.Create); BinaryFormatter bf = new BinaryFormatter(); Response.Write("Writing Employee Info:"); bf.Serialize(steam,mp); steam.Close(); mp = null; //反序列化 Stream steam2 = File.Open("c:\\temp3.dat", FileMode.Open); BinaryFormatter bf2 = new BinaryFormatter(); Response.Write("Reading Employee Info:"); Employee mp2 = (Employee)bf2.Deserialize(steam2); steam2.Close(); Response.Write(mp2.EmpId); Response.Write(mp2.EmpName); Response.Write(mp2.NoSerialString); }
在格式化程序上調用 Serialize 方法時,對象序列化按照以下規則進行:
檢查格式化程序是否有代理選取器。如果有,檢查代理選取器是否處理指定類型的對象。如果選取器處理此對象類型,將在代理選取器上調用 ISerializable.GetObjectData。
如果沒有代理選取器或有卻不處理此類型,將檢查是否使用 Serializable 屬性對對象進行標記。如果未標記,將會引發 SerializationException。
如果對象已被正確標記,將檢查對象是否實現了 ISerializable。如果已實現,將在對象上調用 GetObjectData。
如果對象未實現 Serializable,將使用默認的序列化策略,對所有未標記為 NonSerialized 的字段都進行序列化。
版本控制
.NET 框架支持版本控制和並排執行,並且,如果類的接口保持一致,所有類均可跨版本工作。由於序列化涉及的是成員變量而非接口,所以,在向要跨版本序列化的類中添加成員變量,或從中刪除變量時,應謹慎行事。特別是對於未實現 ISerializable 的類更應如此。若當前版本的狀態發生了任何變化(例如添加成員變量、更改變量類型或更改變量名稱),都意味着如果同一類型的現有對象是使用早期版本進行序列化的,則無法成功對它們進行反序列化。
如果對象的狀態需要在不同版本間發生改變,類的作者可以有兩種選擇:
實現 ISerializable。這使您可以精確地控制序列化和反序列化過程,在反序列化過程中正確地添加和解釋未來狀態。
使用 NonSerialized 屬性標記不重要的成員變量。僅當預計類在不同版本間的變化較小時,才可使用這個選項。例如,把一個新變量添加至類的較高版本后,可以將該變量標記為 NonSerialized,以確保該類與早期版本保持兼容。
序列化規則
由於類編譯后便無法序列化,所以在設計新類時應考慮序列化。需要考慮的問題有:是否必須跨應用程序域來發送此類?是否要遠程使用此類?用戶將如何使用此類?也許他們會從我的類中派生出一個需要序列化的新類。只要有這種可能性,就應將類標記為可序列化。除下列情況以外,最好將所有類都標記為可序列化:
所有的類都永遠也不會跨越應用程序域。如果某個類不要求序列化但需要跨越應用程序域,請從 MarshalByRefObject 派生此類。
類存儲僅適用於其當前實例的特殊指針。例如,如果某個類包含非受控的內存或文件句柄,請確保將這些字段標記為 NonSerialized 或根本不序列化此類。
某些數據成員包含敏感信息。在這種情況下,建議實現 ISerializable 並僅序列化所要求的字段。
以上部分內容並非原創有參考其他文章。