一、概述
序列化是把對象轉變成流。相反的過程就是反序列化。
哪些場合用到這項技術呢?
1. 把對象保存到本地,下次運行程序時恢復這個對象。
2. 把對象傳送到網絡的另一台終端上,然后在此終端還原這個對象。
3. 復制系統的粘帖板中,然后用快捷鍵Ctrl+V恢復這個對象。
常用的序列化流有Binary(二進制流),XML,SOAP。
二、序列化和反序列化使用事例:
這里我們把序列化和反序列化以功能類的形式展現:
public class Serializer { //將類型序列化為字符串 public static string Serialize<T>(T t) where T : class { using(MemoryStream stream=new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, t); return System.Text.Encoding.UTF8.GetString(stream.ToArray()); } } //將類型序列化為文件 public static void SerializeToFile<T>(T t, string path, string fullName) where T : class { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string fullPath = string.Format(@"{0}\{1}", path, fullName); using (FileStream stream = new FileStream(fullPath,FileMode.OpenOrCreate)) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, t); stream.Flush(); } } //將類型序列化為文件 public static void SerializeToFileByXml<T>(T t, string path, string fullName) where T : class { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string fullPath = string.Format(@"{0}\{1}", path, fullName); using (FileStream stream = new FileStream(fullPath, FileMode.OpenOrCreate)) { XmlSerializer formatter = new XmlSerializer(typeof(T)); formatter.Serialize(stream, t); stream.Flush(); } } //將字符串反序列化為類型 public static TResult Deserialize<TResult>(string s) where TResult : class { byte[] bs = System.Text.Encoding.UTF8.GetBytes(s); using (MemoryStream stream = new MemoryStream(bs)) { BinaryFormatter formatter = new BinaryFormatter(); return formatter.Deserialize(stream) as TResult; } } //將文件反序列化為類型 public static TResult DeserializeFromFile<TResult>(string path) where TResult : class { using (FileStream stream = new FileStream(path,FileMode.Open)) { BinaryFormatter formatter = new BinaryFormatter(); return formatter.Deserialize(stream) as TResult; } } //將xml文件反序列化為類型 public static TResult DeserializeFromFileByXml<TResult>(string path) where TResult : class { using (FileStream stream = new FileStream(path, FileMode.Open)) { XmlSerializer formatter = new XmlSerializer(typeof(TResult)); ; return formatter.Deserialize(stream) as TResult; } } }
上面事例中的方法是以泛型方法實現的,其中附加了泛型約束,保證泛型安全。
序列化功能類有了下面我們建一個Book對象,用它來測試我們的功能類。
[Serializable] public class Book { [NonSerialized] private string _bookPwd; [field: NonSerialized] public event EventHandler NameChanged; private string _bookName; private string _bookID; public ArrayList alBookReader; public string _bookPrice; public Book() { alBookReader = new ArrayList(); } public string BookName { get { return _bookName; } set { if (NameChanged != null) { NameChanged(this, null); } _bookName = value; } } public void BookPwd(string pwd) { _bookPwd=pwd; } public string BookID { get { return _bookID; } set { _bookID = value; } } public void SetBookPrice(string price) { _bookPrice = price; } [OnDeserializedAttribute] public void changeName(StreamingContext context) { this.BookName = "C#深入淺出"; } public void Write() { Console.WriteLine("Book ID:" + BookID); Console.WriteLine("Book Name:" + BookName); Console.WriteLine("Book Password:" + _bookPwd); Console.WriteLine("Book Price:" + _bookPrice); Console.WriteLine("Book Reader:"); for (int i = 0; i < alBookReader.Count; i++) { Console.WriteLine(alBookReader[i]); } } }
關鍵介紹:
1.[Serializable]特性定義該類型可以被序列化;
2.[NonSerialized]定義某個屬性不被序列化,即:內部成員被NonSerialized禁止序列化特性標記;
3.[field: NonSerialized]定義事件不被序列化;
4.[OnDeserializedAttribute]當它應用於某個方法時,會指定對象被反序列化后立即執行此方法。
5.[OnDeserializingAttribute]當它應用於某個方法時,會指定對象被反序列化時立即執行此方法。
6.[OnSerializedAttribute]如果將對象圖應用於某個方法時,會指定在序列化該對象圖后是否調用此方法。
7.[OnSerializingAttribute]當它應用於某個方法時,會指定在對象序列化前調用此方法。、
我們用控制台程序實現序列化和反序列化:
static void Main(string[] args) { string path = "c:\\Test\\"; Book book = new Book(); book.NameChanged += new EventHandler(make_NameChanged); book.BookID = "2001"; book.alBookReader.Add("Abel"); book.alBookReader.Add("Tomson"); book.BookName = "敏捷無敵"; book.BookPwd("*****"); book.SetBookPrice("102.00"); //對象序列化 Serializer.SerializeToFileByXml<Book>(book, path, "book.txt"); //對象反序列化 Book anothorbookserialize = new Book(); anothorbookserialize = Serializer.DeserializeFromFileByXml<Book>(path + "book.txt"); anothorbookserialize.Write(); Console.ReadKey(); } static void make_NameChanged(object sender, EventArgs e) { Console.WriteLine("Name Changed"); }
我們的事例是調用XML序列化流文件形式,序列化執行后的文件如下:
<?xml version="1.0"?>
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<alBookReader>
<anyType xsi:type="xsd:string">Abel</anyType>
<anyType xsi:type="xsd:string">Tomson</anyType>
</alBookReader>
<_bookPrice>102.00</_bookPrice>
<BookName>敏捷無敵</BookName>
<BookID>2001</BookID>
</Book>
反序列化輸出如下:
Name Changed
Book ID:2001
Book Name:敏捷無敵
Book Password:
Book Price:102.00
Book Reader:
Abel
Tomson
結果分析:
[NonSerialized]Book Password屬性在序列化XML文件中沒有出現Book Password。
[field: NonSerialized]NameChanged事件,在序列化XML文件中沒有出現NameChanged。
[OnDeserializedAttribute]changeName()方法,執行反序列化后沒有立即執行changeName()方法,Book Name名稱沒有改變,需要通過Binary流形式才能成功執行方法。
//序列化 Serializer.SerializeToFile<Book>(book, path, "book.txt"); //反序列化 anothorbookserialize = Serializer.DeserializeFromFile<Book>(path + "book.txt");
我們以XML流為例是為了更好的理解序列化和反序列化的執行過程,實際應用中多數以Binary流形式實現序列化和反序列化較多。
三、繼承ISerializable接口更靈活的控制序列化過程:
當以上Serializable特性無法滿足復雜的序列化過程時就需要實現ISerializable接口了。
以下是格式化器的工作流程:如果格式化器在序列化一個對象的時候,發現對象實現了ISerializable接口,那他會忽略類所有序列化特性,轉而調用GetObjectData方法的一個SerializationInfo對象,方法內部負責該對象屬性的添加。反序列化時調用該對象受保護帶參數構造方法中的一個SerializationInfo對象,方法內部對象屬性賦值。
下面我們實現ISerializable接口的子類型應負責父類型的序列化為例:
1.父類同樣實現了ISerializable接口
[Serializable] public class Person:ISerializable { public string Name{get;set;} public Person() { } protected Person(SerializationInfo info,StreamingContext context) { Name = info.GetString("Name"); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name",Name); } } [Serializable] public class Employee: Person , ISerializable { public int Salary{get;set;}
public Employee() { } protected Employee(SerializationInfo info, StreamingContext context) { Salary = info.GetInt32("Salary"); Name = info.GetString("Name"); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info,context); info.AddValue("Salary", Salary); } }
注意:Employee中的GetObjectData方法覆蓋了基類Person中的虛方法GetObjectData。
Employee employee = new Employee() { Name = "Abel", Salary=1220 }; BinarySerializer.SerializeToFile<Employee>(employee, strFile, "employee.txt"); employee = BinarySerializer.DeserializeFromFile<Employee>("c:\\Test\\employee.txt"); Console.WriteLine(employee.Name); Console.WriteLine(employee.Salary);
2.若父類沒有實現了ISerializable接口如何處理呢?
我們要實現繼承ISerializable接口的Employee類的一個父類Person,Person沒有實現ISerializable接口,序列化器沒有默認去處理Person對象,只能由我們自己去做。
下面我們用具體實例實現:
[Serializable] public class Person { public string Name{get;set;} } [Serializable] public class Employee: Person , ISerializable { public int Salary{get;set;} public Employee() { } protected Employee(SerializationInfo info, StreamingContext context) { Salary = info.GetInt32("Salary"); Name = info.GetString("Name"); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", Name); info.AddValue("Salary", Salary); } }
在這此序列化學習中我們用到了事件、泛型和流文件的處理知識,通過序列化我們可以實現本地加載,遠程還原對象。
得到了一個Serializer工具類,該工具類封裝了序列化和反序列化的過程。