序列化

本文转自:http://blog.csdn.net/shanyongxu/article/details/51164822,楼主无私奉献,编著重点,请点击链接查看原文,尊重楼主版权啊亲!


序列化

 

基本操作

 

流属于一种基础的概念和功能,就好像stringint这些基元类型一样,在很多场合下都会用到.在前面通过文件复制,读取文件内容的实例讲述了流的基本概念和操作.本节将会讲述他的另一个应用场景,序列化和反序列化.

 

当程序运行时,需要访问和处理数据,在面向对象编程中,这些数据通常保存在对象中;当程序关闭或者销毁时,这些数据需要保存到某处以便日后重建对象时能够还原对象的状态.在很多应用程序中,都会选择使用数据库用作数据存储,但是如果没有数据库,如何将对象机器状态保存起来,以便下次运行程序时使用呢?

 

将对象及其状态保存起来,就称为序列化,最简单和常见的一种情况就是将对象以及状态保存到文件中;而反序列化不用我说了吧,将文件还原为对象.”序列化”这个词很抽象,我刚开始也不是很理解.不着急,慢慢来.

 

序列化的过程就是将前面流操作的byte[]数组替换成了对象,转换的过程就是将对象变为一个个的字节序列,这个字节序列可以是人们不可读的二进制文件,也可以是人类可读的文本文件.

 

.NET提供了一个接口System.Runtime.Serialization.IFormatter,定义了实现序列化和反序列化的操作,并且提供了两个实现了这个接口的类:BinaryForamtterSoapFormatter.

 

BinaryFormatter位于System.Runtime.Serialization.Formatter.Binary命名空间下,用于将对象序列化为二进制数据;

SoapFormatter位于System.Runtime.Serialization.Formatter.Soap命名空间下,用于将对象序列化为人类直接可读的文件,这个文本使用SOAP来描述的.


SOAP的全称为简单对象访问协议,是一种轻量级的,基于XML协议.有了BinaryFormatterSoapFormatter这两个类,开发者使用它们就可以应付大多数的情况,不用去创建一个类型来实现这个接口.IFormatter中最重要的两个方法就是SerializeDeserialize,分别用于序列化和反序列化,由于它们接收的是Stream基类,因此可以序列化到任何流类型中,而不仅限于文件流.

 

下面使用BinaryFormatter来序列化一个Product对象.(只保留了三个属性):

using System;  
using System.Collections.Generic;  
using System.IO;  
using System.Linq;  
using System.Runtime.Serialization;  
using System.Runtime.Serialization.Formatters.Binary;  
using System.Text;  
using System.Threading.Tasks;  
   
namespace 流  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            IFormatter formatter = new BinaryFormatter();  
            Product item = new Product(188) {Price=2999F,Name="SYX" };  
            Stream fs = File.OpenWrite(@"F:\product.obj"); ;  
            formatter.Serialize(fs,item);  
            fs.Dispose();  
        }  
    }  
   
    public class Product  
    {  
        public int Id { get; set; }  
        public string Name { set; get; }  
        public double Price { set; get; }  
   
        public Product(int id)  
        {  
            this.Id = id;  
        }  
   
        public override string ToString()  
        {  
            return string.Format("Id:{0} , Name:{1} , Price:{2} , ",this.Id,this.Name,this.Price);  
        }  
    }  
}  

如果直接运行的话,会出现一个异常”Product未标记为可序列化”.这是因为在默认情况下类型都是不可序列化的,需要明确的指定Product类是可序列化的.完成这个操作只需要为Product类加一个Serializable特性:

[Serializable]  
    public class Product  
    {  
        public int Id { get; set; }  
        public string Name { set; get; }  
        public double Price { set; get; }  
   
        public Product(int id)  
        {  
            this.Id = id;  
        }  
   
        public override string ToString()  
        {  
            return string.Format("Id:{0} , Name:{1} , Price:{2} , ",this.Id,this.Name,this.Price);  
        }  
}  
再次运行代码就可以得到序列化后的 product.obj 文件了 , 可以用记事本打开看看 .

 

由于product.obj保存的是二进制数据,文本编辑器无法将其解析为合适的字符,所以会出现了乱码.还有一部分是按字符形式直接保存的,就显示为了相应的字符,可以看到对象的属性的属性名称和值.不止一位只要为类型加上Serializable特性就可以了,序列化不管需要该类型是标记为Serializable,类型中的属性和字段也需要是可序列化的.

如果在Product中加入了一个不可序列化的属性,比如一个private类型SqlConnection,因为SqlConnection没有使用Serializable特性进行标记,那么上面的程序就又无法进行的:

[Serializable]  
public class Product  
{  
    public int Id { get; set; }  
    public string Name { set; get; }  
    public double Price { set; get; }  
  
    private SqlConnection conn;  
    public Product(int id)  
    {  
        this.Id = id;  
        conn = new SqlConnection(@"Data Source=.;Initial Catalog=DB;User ID=sa;Password=123");  
    }  
  
    public override string ToString()  
    {  
        return string.Format("Id:{0} , Name:{1} , Price:{2} , Conn:{3}",this.Id,this.Name,this.Price,this.conn==null?"null":"object");  
    }  
}  

此时,就需要向编译器说明一件事:Product类型中,Conn字段不需要进行序列化,在序列化时把它排除在外,.NET框架提供了Nonserialized特性用作这一用途:

[NonSerialized]  
private SqlConnection conn;  

肯定有人会问,这个序列化到底有啥用处?前面不是说了吗,保存对象的状态或者一些信息.有一点需要注意,NonSerialized只能加在紫断殇,不能加在属性上.在完成了这一过程后,再次运行序列化的代码就可以运行了,接下来输出一下Product的状态:

IFormatter formatter = new BinaryFormatter();  
Stream fs = File.OpenRead(@"F:\product.obj");  
Product item = (Product)formatter.Deserialize(fs);  
fs.Dispose();  
Console.WriteLine(item);  

注意:字段是私有的也是可以序列化的,这是因为序列化的底层是通过反射来实现的,而反射可以访问到私有字段.关于反射,咱们会抽出一章来专门讲解.

 

尽管反序列化没有抛出异常,还是发现变成了null,这是因为反序列化并不会调用对象的构造函数,此时,由于Conn没有进行序列化,使序列化前对象和反序列化后得到的对象状态变得不一样了..NET提供了IDeserializationCallback接口来完成这件事:

public interface IDeserializationCallback  
{  
void OnDeserialization(object ssender);  
} 

从接口名称中的Callback可以看出,这个接口是一个回调函数,在对象反序列化之后调用.sender总是传入null,可以不理会.现在让Product实现这个接口,以实现conn的序列化:

public class Product : IDeserializationCallback  
{  
    public void OnDeserialization(object sender)  
    {  
        conn = new SqlConnection(@"Data Source=.;Initial Catalog=DB;User ID=sa;Password=123");  
    }  
}  

此时在执行上面反序列化的代码,可以得到期望的结果.

 

SoapFormatter类定义在命名空间下,它的使用方式和BinaryFormatter完全相同.使用SOAP的优点是可以跨平台,因为SOAP是一个开放的协议,所以使用非Windows平台下的其他程序也可以处理它.但是SOAP使用XML来描述的,因此文件的尺寸比较大.下面的代码使用SoapFormatter来序列化Product:

//使用SoapFormatter序列化  
IFormatter formatter = new SoapFormatter();  
Product item = new Product(188) {Name="ZSF",Price=4567F };  
Stream fs = File.OpenWrite(@"F:\product.soap");  
formatter.Serialize(fs,item);  
fs.Dispose();  

注意一点,在引用命名空间的时候,你需要在项目的引用中手动添加引用才能使该命名空间生效,如果你直接写上该命名空间的话是会报错的.

 

使用记事本打开product.soap文件,应该会看到一个XML文件样式的内容.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值