序列化

 序列化

 

基本操作

 

本来楼主是想学点安卓的知识的,楼主发现学习一门新知识需要付出的代价太大了,加上学的太多,就没有一门精通的了,所以楼主暂时的想法是先把关于.NET的知识学透了.

 

流属于一种基础的概念和功能,就好像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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值