C# 序列化与反序列化

一、概述

  当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。

  把对象转换为字节序列的过程称为对象的序列化。

  把字节序列恢复为对象的过程称为对象的反序列化。

  二、对象的序列化主要有两种用途:

  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

  我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁 盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable(请参阅基 本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。

  2) 在网络上传送对象的字节序列。

  对象仅在创建对象的应用程序域中有效。除非对象是从MarshalByRefObject派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。如果对象是从MarshalByRefObject派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从MarshalByRefObject派生得到的对象标记为Serializable。远程使用此对象时,负责进行序列化并已预先配置为SurrogateSelector的格式化程序将控制序列化过程,并用一个代理替换所有从MarshalByRefObject派生得到的对象。如果没有预先配置为SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则.

  三、.NET提供了三种序列化方式

  [1]、XML Serializer

  [2]、SOAP Serializer

  [3]、BinarySerializer

  四、基本序列化

  要使一个类可序列化,最简单的方法是使用 Serializable 属性对它进行标记,如下所示:

1、使用BinaryFormatter进行串行化
    下面是一个可串行化的类:
    
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.IO;
using
 System.Runtime.Serialization.Formatters.Binary;
/// <summary>
/// ClassToSerialize 的摘要说明
/// </summary>

[Serializable]
public   class  ClassToSerialize
{
    
public int id = 100;
    
public string name = "Name";
    [NonSerialized]
    
public string Sex = "";
}


    下面是串行化和反串行化的方法:
    
public   void  SerializeNow()
    
{
        ClassToSerialize c 
= new ClassToSerialize();
        FileStream fileStream 
= new FileStream("c:\\temp.dat", FileMode.Create);
        BinaryFormatter b 
= new BinaryFormatter();
        b.Serialize(fileStream, c);
        fileStream.Close();
    }

    
public   void  DeSerializeNow()
    
{
        ClassToSerialize c 
= new ClassToSerialize();
        c.Sex 
= "kkkk";
        FileStream fileStream 
= new FileStream("c:\\temp.dat", FileMode.Open, FileAccess.Read, FileShare.Read);
        BinaryFormatter b 
= new BinaryFormatter();
        c 
= b.Deserialize(fileStream) as ClassToSerialize;
          Response.Write(c.name);
        Response.Write(c.Sex);
        fileStream.Close();
    }

    调用上述两个方法就可以看到串行化的结果:Sex属性因为被标志为[NonSerialized],故其值总是为null。
     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);
    }

这就是自带的序列化和反序列的操作,但是,很多情况下,一个对象比较大,而且很多私有的属性和方法我们不需要,例如在原型模式里面序列化的话,只需要序列Clone方法和一些属性,私有的方法无需要,还例如在读取大规模的IO的时候,读取操作完全不需要... 这时候就需要自己集成重写序列的ISerializable接口:


 实现该接口需要两个注意的,一个就是构造函数,主要是为了反序列,另一个就是GetObjectData,主要是执行序列化,例如我们现在有一个Employee类需要序列化    [Serializable()]    //Set this attribute to all the classes that want to serialize
    public class Employee : ISerializable //derive your class from ISerializable {
        public int EmpId;
        public string EmpName;
        [NonSerialized()]
    public string NoSerialString="NoSerialString-Test";

}
,需要注意的是我这里的NoSerialString属性前面有[NonSerialized()],就是说默认并不序列化这个属性,而是使用默认值 。

 首先是构造函数:        public Employee(SerializationInfo info, StreamingContext ctxt)
        {
            EmpId = (int)info.GetValue("EmployeeId", typeof(int));
            EmpName = (String)info.GetValue("EmployeeName", typeof(string));
            //NoSerialString = (String)info.GetValue("NoSerialString", typeof(string));
        }


然后是序列化方法,就是当写入流的时候怎么保存的:
        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            //You can use any custom name for your name-value pair. But make sure you
            // read the values with the same name. For ex:- If you write EmpId as "EmployeeId"
            // then you should read the same with "EmployeeId"
            info.AddValue("EmployeeId", EmpId);
            info.AddValue("EmployeeName", EmpName);
        }


把上面两个方法写入到Employee类,然后写个测试的程序:
public class ObjSerial{
    public static void Main(String[] args){
        Employee mp = new Employee();
        mp.EmpId = 10;
        mp.EmpName = "Omkumar";
        mp.NoSerialString = "你好啊";
               
       //序列化
        Stream stream = File.Open("EmployeeInfo.osl", FileMode.Create);
        BinaryFormatter bformatter = new BinaryFormatter();
               
        Console.WriteLine("Writing Employee Information");
        bformatter.Serialize(stream, mp);
        stream.Close();

 


        mp = null;
       //反序列
        stream = File.Open("EmployeeInfo.osl", FileMode.Open);
        bformatter = new BinaryFormatter();
           
        Console.WriteLine("Reading Employee Information");
        mp = (Employee)bformatter.Deserialize(stream);
        stream.Close();
               
        Console.WriteLine("Employee Id: {0}",mp.EmpId.ToString());
        Console.WriteLine("Employee Name: {0}",mp.EmpName);
        Console.WriteLine("Employee NoSerialString: {0}",mp.NoSerialString);

    }
}


执行的结果是:Writing Employee Information
Reading Employee Information
Employee Id: 10
Employee Name: Omkumar
Employee NoSerialString: NoSerialString-Test
 
 看到Employee NoSerialString:属性的值没有,它保持默认值,没有序列化。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值