.net序列化概念以及应用

来源:互联网 发布:发那科机械手编程 编辑:程序博客网 时间:2024/06/04 00:28

序列化

序列化是指将对象实例的状态存储到存储媒体中的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

在面向对象的环境中实现序列化的机制时,必须在易用性和灵活性之间进行一些权衡。只要对此过程有足够的控制能力,就可以使该过程在很大程度上自动进行。例如,简单的二进制序列化不能满足需要,或者,由于特定原因需要确定类中那些字段需要序列化。下面将探讨 .NET 框架提供的可靠的序列化机制,并着重介绍自定义序列化过程的一些重要功能。

序列化类型

.NET Framework提供两种序列化技术。
(1)二进制序列化:可以保持类型不变,即可以在应用程序的不同调用之间保留对象的状态。
(2)XML和SOAP序列化:仅序列化公共属性和字段,不保存类型。

二进制序列化

序列化可被定义为将对象的状态存储到存储媒介中的过程。在此过程中,对象的公共字段和私有字段以及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流。在以后反序列化该对象时,创建原始对象的精确复本。

序列化有两个重要的功能:一个是将对象的状态保持在存储媒体中,以便在以后可以重新创建精确的副本;另一个是通过值将对象从一个应用程序域发送到另一个应用程序域中。例如,序列化可用于在ASP.NET中保存会话状态并将对象复制到Windows窗体的程序域中。

基本序列化

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

        [Serializable]        public class MyObject {             public int n1 = 0;             public int n2 = 0;             public String str = null;        }

以下代码片段说明了如何将此类的一个实例序列化为一个文件:

        MyObject obj = new MyObject();        obj.n1 = 1;        obj.n2 = 24;        obj.str = "一些字符串";        IFormatter formatter = new BinaryFormatter();        Stream stream = new FileStream("MyFile.bin", FileMode.Create,        FileAccess.Write, FileShare.None);        formatter.Serialize(stream, obj);        stream.Close();

本例使用二进制格式化程序进行序列化。您只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。类中的所有成员变量(甚至标记为 private 的变量)都将被序列化,但这一点在本例中未明确体现出来。在这一点上,二进制序列化不同于只序列化公共字段的 XML 序列化程序。

将对象还原到它以前的状态也非常容易。首先,创建格式化程序和流以进行读取,然后让格式化程序对对象进行反序列化。以下代码片段说明了如何进行此操作。

        IFormatter formatter = new BinaryFormatter();        Stream stream = new FileStream("MyFile.bin", FileMode.Open,        FileAccess.Read, FileShare.Read);        MyObject obj = (MyObject) formatter.Deserialize(fromStream);        stream.Close();        // 下面是证明        Console.WriteLine("n1: {0}", obj.n1);        Console.WriteLine("n2: {0}", obj.n2);        Console.WriteLine("str: {0}", obj.str);

上面所使用的 BinaryFormatter 效率很高,能生成非常紧凑的字节流。所有使用此格式化程序序列化的对象也可使用它进行反序列化,对于序列化将在 .NET 平台上进行反序列化的对象,此格式化程序无疑是一个理想工具。需要注意的是,对对象进行反序列化时并不调用构造函数。对反序列化添加这项约束,是出于性能方面的考虑。但是,这违反了对象编写者通常采用的一些运行时约定,因此,开发人员在将对象标记为可序列化时,应确保考虑了这一特殊约定。

选择性序列化

类通常包含不应被序列化的字段。例如,假设某个类用一个成员变量来存储线程 ID。当此类被反序列化时,序列化此类时所存储的 ID 对应的线程可能不再运行,所以对这个值进行序列化没有意义。可以通过使用 NonSerialized 属性标记成员变量来防止它们被序列化,如下所示:

        [Serializable]        public class MyObject        {             public int n1;             [NonSerialized] public int n2;             public String str;        }

自定义序列化

可以通过在对象上实现 ISerializable 接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。要实现 ISerializable,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。以下代码示例说明了如何在前一部分中提到的 MyObject 类上实现 ISerializable。

        [Serializable]        public class MyObject : ISerializable        {             public int n1;             public int n2;             public String str;             public MyObject()             {             }             protected MyObject(SerializationInfo info, StreamingContext context)             {               n1 = info.GetInt32("i");               n2 = info.GetInt32("j");               str = info.GetString("k");             }             public virtual void GetObjectData(SerializationInfo info, StreamingContext context)             {               info.AddValue("i", n1);               info.AddValue("j", n2);               info.AddValue("k", str);             }        }

在序列化过程中调用 GetObjectData 时,需要在方法调用中提供 SerializationInfo 对象。只需按名称/值对的形式添加要序列化的变量,其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象,便可以自由选择添加至 SerializationInfo 的成员变量。如果基对象实现了 ISerializable,则派生类应调用其基对象的 GetObjectData 方法。

需要强调的是,将 ISerializable 添加至某个类时,需要同时实现 GetObjectData 以及特殊的构造函数。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反序列化某个类,将会出现异常。在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData 方法。例如,如果将 SetObjectData 方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不编写代码来防止多次调用 SetObjectData 方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程序却调用此对象的 SetObjectData 方法,将会引起一些潜在的麻烦。

在反序列化过程中,使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反序列化时,对构造函数的任何可见性约束都将被忽略,因此,可以将类标记为 public、protected、internal 或 private。一个不错的办法是,在类未封装的情况下,将构造函数标记为 protect。如果类已封装,则应标记为 private。要还原对象的状态,只需使用序列化时采用的名称,从 SerializationInfo 中检索变量的值。如果基类实现了 ISerializable,则应调用基类的构造函数,以使基础对象可以还原其变量。

如果从实现了 ISerializable 的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须同时实现构造函数以及 GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操作。

        [Serializable]        public class ObjectTwo : MyObject        {             public int num;             public ObjectTwo() : base()             {             }             protected ObjectTwo(SerializationInfo si, StreamingContext context) :        base(si,context)             {               num = si.GetInt32("num");             }             public override void GetObjectData(SerializationInfo si,StreamingContext context)             {               base.GetObjectData(si,context);               si.AddValue("num", num);             }        }

切记要在反序列化构造函数中调用基类,否则,将永远不会调用基类上的构造函数,并且在反序列化后也无法构建完整的对象。

对象被彻底重新构建,但是在反系列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用。如果正在进行反序列化的类实现了 IDeserializationCallback,则反序列化整个对象图表后,将自动调用 OnSerialization 方法。此时,引用的所有子对象均已完全还原。有些类不使用上述事件侦听器,很难对它们进行反序列化,散列表便是一个典型的例子。在反序列化过程中检索关键字/值对非常容易,但是,由于无法保证从散列表派生出的类已反序列化,所以把这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方法。

反序列化

下面用代码演示了该类的实例是如何被序列化到一个二进制文件(.bin)中的。

代码
AuthUserEntry user=new AuthUserEntry();
user.AccountId=8712590;
user.AccountName=”admin”;
IFormatter formater=new BinaryFormatter();
Stream stream=new FileStream(“UserInfo.bin”,FileMode.Create,FileAccess.Write,FileShare.None);
formater.Serialize(stream,user);
stream.Close();
在这段代码中,创建流的实例和使用的格式接口后,对该格式接口调用Serialize方法,类中的所有成员变量都将被序列化,即使是那些已被标记为私有的变量。

2.反序列化
要将对象还原回其以前的状态,首先,创建用于读取的流和格式化接口,然后再用格式化接口反序列化该对象。下面的代码示例说明如何执行上述的操作。

代码

 IFormatter formater=new BinaryFormatter(); Stream stream=new FileStream("UserInfo.bin",FileMode.Open,FileAccess.Read,FileShare.Read); AuthUserEntry me=(AuthUserEntry) formater.Deserialize(stream); stream.Close();

XML和SOAP序列化

也可以对XML和SOAP流对象进行序列化和反序列化,这种操作一般用于将对象转换为网络中容易传输的格式。例如,可以序列化一个对象,然后使用HTTP通过Internet在客户端和服务器之间传输该对象;在另一端,通过反序列化重新构造对象。

XML序列化仅将对象的公共字段和属性值序列化为XML流,而不转换方法、索引器、私有字段或只读属性(只读集合除外)。若要序列化对象的所有字段和属性(公共的和私有的),可以使用BinaryFormatter序列化。XML序列化不包括类型信息,即不能保证序列化后的对象在被反序列化时,变为同一类型的对象。

XML序列化中最主要的类是XmlSerializer类,它的最重要的方法是Serialize和Deserialize。XmlSerializer生成的XML流符合万维网联合会(www.w3.org)XML架构定义语言(XSD)的建议。 

使用XmlSerializer类,可将下列各项序列化。

•公共类的公共读/写属性和字段。
•实现ICollection或IEnumerable的类(注意:只有集合会被序列化,而公共属性却不会)。
•XmlElement对象。
•XmlNode对象。
•DataSet对象。
1、序列化对象

首先,创建要序列化的对象并设置它的公共属性和字段,而且必须确定用以存储XML流的传输格式(或者作为流,或者作为文件)。例如,如果XML流必须以永久形式保存,则创建FileStream对象。当反序列化对象时,传输格式将确定创建流还是文件对象。确定了传输格式之后,就可以根据需要调用Serialize或Deserialize方法。

其次,调用该对象的类型构造XmlSerialize方法。

最后,调用Serialize方法以生成对象的公共属性和字段的XML流表示形式或文件表示形式。

下面事例是创建一个对象:

代码
//第一步,创建要序列化的对象
AuthUserEntry user=new AuthUserEntry();
user.AccountId=8712590;
user.AccountName=”admin”;
//第二步,构造XmlSeriaLizer对象
XmlSerializer mySerializer=new XmlSerializer(typeof(AuthUserEntry));
//创建Stream Writer对象完成对文件的写操作
StreamWriter myWriter=new StreamWriter(“UserInfo.xml”);
//第三步,调用Serialize方法实现序列化
mySerializer.Serialize(myWriter,user);
myWriter.Close();

反序列化对象

首先,使用要反序列化的对象的类型构造XmlSerializer。
其次,调用Deserialize方法以产生该对象的副本。在反序列化时,必须将返回的对象强制转换为原始对象的类型。
将对象反序列化为文件的语句为:

代码
AuthUserEntry me;
//用对象类型构造XmlSerializer的实例
XmlSerializer mySerializer=new XmlSerializer(typeof(AuthUserEntry));
//创建FileStream读取文件
FileStream myFileStream=new FileStream(“UserInfo.xml”,FileMode.Open);
//调用Deserialize方法并强制转换返回对象的类型
me=(AuthUserEntry) mySerializer.Deserialize(myFileStream);
myFileStream.Close();

例子:Xml序列化和反序列化的方法
代码

 using System; using System.IO; using System.Xml.Serialization; namespace XMLSerializableExample {    [Serializable]    public class AuthUserEntry    {      private string accountName;      private int accountId;      public string AccountName      {         get         {            return accountName;         }         set         {            accountName=value;         }       }       public int AccountId       {         get          {            return accountId;         }         set         {            accountId=value;         }       }    }    class Account    {       static void Main(string[] args)       {          //第一步,创建要序列化的对象          AuthUserEntry user=new AuthUserEntry();          user.AccountId=8712590;          user.AccountName="admin";          //第二步,构造XmlSeriaLizer对象          XmlSerializer mySerializer=new XmlSerializer(typeof(AuthUserEntry)); //创建Stream Writer对象完成对文件的写操作          StreamWriter myWriter=new StreamWriter("UserInfo.xml");          //第三步,调用Serialize方法实现序列化          mySerializer.Serialize(myWriter,user);          myWriter.Close();          AuthUserEntry me;          //创建FileStream读取文件          FileStream myFileStream=new FileStream("UserInfo.xml",FileMode.Open);          //调用Deserialize方法并强制转换返回对象的类型          me=(AuthUserEntry) mySerializer.Deserialize(myFileStream);          myFileStream.Close();          Console.WriteLine("帐户号:{0}",me.AccountId);          Console.WriteLine("帐户名:{0}",me.AccountName);          //按回车键结束          Console.ReadLine();        }     } }
0 0
原创粉丝点击