1. 程式人生 > 程式設計 >淺談.Net中的序列化和反序列化

淺談.Net中的序列化和反序列化

序列化和反序列化相信大家都經常聽到,也都會用, 然而有些人可能不知道:.net為什麼要有這個東西以及.net Frameword如何為我們實現這樣的機制, 在這裡我也是簡單談談我對序列化和反序列化的一些理解。

一、什麼序列化和反序列化

序列化通俗地講就是將一個物件轉換成一個位元組流的過程,這樣就可以輕鬆儲存在磁碟檔案或資料庫中。反序列化是序列化的逆過程,就是將一個位元組流轉換回原來的物件的過程。

然而為什麼需要序列化和反序列化這樣的機制呢?這個問題也就涉及到序列化和反序列化的用途了,

對於序列化的主要用途有:

  • 將應用程式的狀態儲存在一個磁碟檔案或資料庫中,並在應用程式下次執行時恢復狀態。例如,Asp.net 中利用序列化和反序列化來儲存和恢復會話狀態。
  • 一組物件可以輕鬆複製到Windows 窗體的剪貼簿中,再貼上回同一個或者另一個應用程式。
  • 將物件按值從一個應用程式域中傳送到另一個程式域

並且如果把物件序列化成記憶體中的位元組流,就可以利用一些其他的技術來處理資料,例如,對資料進行加密和壓縮等。

二、序列化和反序列簡單使用

.Net Framework 提供二種序列化方式:

  • 二進位制序列化
  • XML 和SOAP序列化

序列化和反序列化的簡單使用:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Serializable
{
  [Serializable]
  public class Person
  {
    public string personName;

    [NonSerialized]
    public string personHeight;

    private int personAge;
    public int PersonAge
    {
      get { return personAge; }
      set { personAge = value; }
    }

    public void Write()
    {
      Console.WriteLine("Person Name: "+personName);
      Console.WriteLine("Person Height: " +personHeight);
      Console.WriteLine("Person Age: "+ personAge);
    }

  }
  class Program
  {
    static void Main(string[] args)
    {
      Person person = new Person();
      person.personName = "Jerry";
      person.personHeight = "175CM";
      person.PersonAge = 22;
      Stream stream = Serialize(person);

      //為了演示,都重置
      stream.Position = 0;
      person = null;

      person = Deserialize(stream);
      person.Write();
      Console.Read();

    }
    private static MemoryStream Serialize(Person person)
    {
      MemoryStream stream = new MemoryStream();

      // 構造二進位制序列化格式器
      BinaryFormatter binaryFormatter = new BinaryFormatter();
      // 告訴序列化器將物件序列化到一個流中
      binaryFormatter.Serialize(stream,person);

      return stream;

    }

    private static Person Deserialize(Stream stream)
    {
      BinaryFormatter binaryFormatter = new BinaryFormatter();
      return (Person)binaryFormatter.Deserialize(stream);
    }

  }
}

主要是呼叫System.Runtime.Serialization.Formatters.Binary名稱空間下的BinnaryFormatter類來進行序列化和反序列化,呼叫反序列化後的結果截圖:

淺談.Net中的序列化和反序列化

從中可以看出除了標記NonSerialized的其他成員都能序列化,注意這個屬性只能應用於一個型別中的欄位,而且會被派生型別繼承。

SOAP 和XML 的序列化和反序列化和上面類似,只需要改下格式化器就可以了, 這裡我就不列出來了。

三、控制序列化和反序列化

有兩種方式來實現控制序列化和反序列化:

  • 通過OnSerializing,OnSerialized,OnDeserializing,OnDeserialized,NonSerialized和OptionalField等屬性
  • 實現System.Runtime.Serialization.ISerializable介面

第一種方式實現控制序列化和反序列化程式碼:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ControlSerialization
{
  [Serializable]
  public class Circle
  {
    private double radius; //半徑
    [NonSerialized]
    public double area; //面積

    public Circle(double inputradiu)
    {
      radius = inputradiu;
      area = Math.PI * radius * radius;
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
      area = Math.PI * radius * radius;
    }

    public void Write()
    {
      Console.WriteLine("Radius is: " + radius);
      Console.WriteLine("Area is: " + area);
    }
  }
  class Program
  {

    static void Main(string[] args)
    {
      Circle c = new Circle(10);
      MemoryStream stream =new MemoryStream();
      BinaryFormatter formatter = new BinaryFormatter();
      // 將物件序列化到記憶體流中,這裡可以使用System.IO.Stream抽象類中派生的任何型別的一個物件, 這裡我使用了 MemoryStream型別。
      formatter.Serialize(stream,c);
      stream.Position = 0;
      c = null;
      c = (Circle)formatter.Deserialize(stream);
      c.Write();
      Console.Read();

    }
  }
}

執行結果為:

淺談.Net中的序列化和反序列化

注意:如果註釋掉 OnDeserialized屬性的話,area欄位的值就是0了,因為area欄位沒有被序列化到流中。

在上面需要序列化的物件中,格式化器只會序列化物件的radius欄位的值。area欄位中的值不會序列化,因為該欄位已經應用了NonSerializedAttribute屬性,然後我們用Circle c=new Circle(10)這樣程式碼構建一個Circle物件時,在內部,area會設定一個約為314.159這樣的值,這個物件序列化時,只有radius的欄位的值(10)寫入流中, 但當反序列化成一個Circle物件時,它的area欄位的值會初始化為0,而不是約314.159的一個值。為了解決這樣的問題,所以自定義一個方法應用OnDeserializedAttribute屬性。此時的執行過程為:每次反序列化型別的一個例項,格式化器都會檢查型別中是否定義了 一個應用了該attribute的方法,如果是,就呼叫該方法,呼叫該方法時,所有可序列化的欄位都會被正確設定。除了OnDeserializedAttribute這個定製attribute,system.Runtime.Serialization名稱空間還定義了OnSerializingAttribute,OnSerializedAttribute和OnDeserializingAttribute這些定製屬性。

實現ISerializable介面方式控制序列化和反序列化程式碼:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;

namespace ControlSerilization2
{
  [Serializable]
  public class MyObject : ISerializable
  {
    public int n1;
    public intn2;

    [NonSerialized]
    public String str;

    public MyObject()
    {
    }

    protected MyObject(SerializationInfo info,StreamingContext context)
    {
      n1 = info.GetInt32("i");
      n2 = info.GetInt32("j");
      str = info.GetString("k");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info,StreamingContext context)
    {
      info.AddValue("i",n1);
      info.AddValue("j",n2);
      info.AddValue("k",str);
    }

    public void Write()
    {
      Console.WriteLine("n1 is: " + n1);
      Console.WriteLine("n2 is: " + n2);
      Console.WriteLine("str is: " + str);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      MyObject obj = new MyObject();
      obj.n1 = 2;
      obj.n2 = 3;
      obj.str = "Jeffy";
      MemoryStream stream = new MemoryStream();
      BinaryFormatter formatter = new BinaryFormatter();
      // 將物件序列化到記憶體流中,這裡可以使用System.IO.Stream抽象類中派生的任何型別的一個物件, 這裡我使用了 MemoryStream型別。
      formatter.Serialize(stream,obj);
      stream.Position = 0; 
      obj = null;
      obj = (MyObject)formatter.Deserialize(stream);
      obj.Write();
      Console.Read();
    }
  }
}

結果為:

淺談.Net中的序列化和反序列化

此時的執行過程為:當格式化器序列化物件時,會檢查每個物件,如果發現一個物件的型別實現了ISerializable介面,格式化器會忽視所有定製屬性,改為構造一個新的System.Runtime.Serialization.SerializationInfo物件,這個物件包含了要實際為物件序列化的值的集合。構造好並初始化好SerializationInfo物件後,格式化器呼叫型別的GetObjectData方法,並向它傳遞對SerializationInfo物件的引用,GetObjectData方法負責決定需要哪些資訊來序列化物件,並將這些資訊新增到SerializationInfo物件中,通過呼叫AddValue方法來新增需要的每個資料,新增好所有必要的序列化資訊後,會返回至格式化器,然後格式化器獲取已經新增到SerializationInfo物件中的所有值,並將它們都序列化到流中,當反序列化時,格式化器從流中提取一個物件時,會為新物件分配記憶體,最初,這個物件的所有欄位都設為0或null,然後,格式化器檢查型別是否實現了ISerializable介面,如果存在這個介面, 格式化器就嘗試呼叫一個特殊構造器,它的引數和GetObjectData方法的完全一致。

四、格式化器如何序列化和反序列化

從上面的分析中可以看出,進行序列化和反序列化主要是格式化器在工作的,然而下面就是要講講格式化器是如何序列化一個應用了 SerializableAttribute 屬性的物件。

  1. 格式化器呼叫FormatterServices的GetSerializableMembers方法:public static MemberInfo[] GetSerializableMembers(Type type,StreamingContext context);這個方法利用發射獲取型別的public和private實現欄位(標記了NonSerializedAttributee屬性的欄位除外)。方法返回由MemberInfo物件構成的一個數組,其中每個元素對應於一個可序列化的例項欄位。
  2. 物件被序列化,System.Reflection.MemberInfo物件陣列傳給FormatterServices的靜態方法GetObjectData: public static object[] GetObjectData(Object obj,MemberInfo[] members); 這個方法返回一個Object陣列,其中每個元素都標識了被序列化的那個物件中的一個欄位的值。
  3. 格式化器將程式集標識和型別的完整名稱寫入流中。
  4. 格式化器然後遍歷兩個陣列中的元素,將每個成員的名稱和值寫入流中。

接下來是解釋格式化器如何自動反序列化一個應用了 SerializableAttribute屬性的物件。

  1. 格式化器從流中讀取程式集標識和完整型別名稱。
  2. 格式化器呼叫FormatterServices的靜態方法GetUninitializedObject: public static Object GetUninitializedObject(Type ttype);這個方法為一個新物件分配記憶體,但不為物件呼叫構造器。然而,物件的所有欄位都被初始化為0或null.
  3. 格式化器現在構造並初始化一個MemberInfo陣列,呼叫FormatterServices的GetSerializableMembers方法,這個方法返回序列化好、現在需要反序列化的一組欄位。
  4. 格式化器根據流中包含的資料建立並初始化一個Object陣列。
  5. 將對新分配的物件、MemberInfo陣列以及並行Object陣列的引用傳給FormatterServices的靜態方法PopulateObjectMembers:

public static Object PopulateObjectMembers(Object obj,MemberInfo[] members,Object[] data);這個方法遍歷陣列,將每個欄位初始化成對應的值。

注:格式化如何序列化和反序列物件部分摘自CLR via C#(第三版),寫在這裡可以讓初學者進一步理解格式化器在序列化和反序列化過程中所做的工作。

寫到這裡這篇關於序列化和反序列的文章終於結束了, 希望對自己以後複習和園子裡的朋友有幫助。

以上就是淺談.Net中的序列化和反序列化的詳細內容,更多關於.net 序列化和反序列化的資料請關注我們其它相關文章!