C#高级编程之处理XML-连载十八

时间:2010年04月12日 点击:103

23.9  XML中串行化对象

串行化是把一个对象存入磁盘的过程。应用程序的另一部分,甚至另一个应用程序都可以反串行化对象,使它的状态与串行化之前相同。.NET Framework为此提供了两种方式。

本节将介绍System.Xml.Serialization命名空间。它包含的类可用于把对象串行化为XML文档或流。这表示对象的公共属性和公共字段将转换为XML元素和/或属性。

System.Xml.Serialization命名空间中最重要的类是XmlSerializer。要串行化对象,首先需要实例化一个XmlSerializer对象,指定要串行化的对象类型,然后实例化一个流/写入器对象,把文件写入流/文档。最后一步是在XmlSerializer上调用Serializer()方法,给它传送流/写入器对象和要串行化的对象。

被串行化的数据可以为基本类型的数据、字段、数组和XmlElementsXmlAttribute对象格式的内嵌XML

为了从XML文档中反串行化对象,应执行上述过程的逆过程。即创建一个流/读取器对象和一个XmlSerializer对象,然后给DeSerializer()方法传送该流/读取器对象。这个方法返回反串行化的对象,但需要转换为正确的类型。

注意:

XML串行化器不能转换私有数据,只能转换公共数据,它也不能串行化对象图表。

但是,这并不是一个严格的限制。对类进行仔细设计,就很容易避免突破这个限制。如果需要串行化公共数据和私有数据,以及包含许多嵌套对象的对象图形,就可以使用System. Runtime. Serialization. Formatters.Binary命名空间。

使用System.Xml.Serialization类可以进行的其他工作如下所示:

       确定数据应是一个属性还是元素

       指定命名空间

       改变属性或元素名

对象和XML文档之间的链接是给类加上注释的定制C#属性,这些属性可以告诉串行化程序如何写入数据。.NET Framework中有一个工具xsd.exe,它可以帮助我们创建这些属性。xsd.exe可以完成如下任务:

       XDR模式文件中生成一个XML模式

       XML文件中生成XML模式

       XDS模式文件中生成 DataSet

       生成运行库类,运行库类包含XmlSerialization的定制属性

       从已经开发出来的类中生成XSD

       限制在代码中创建的元素

       确定生成代码的编程语言(C#VB.NETJScript.NET)

       在编译好的程序集中创建类中的模式

参见Framework文档说明书,了解xsd.exe命令行选项的详细内容。

尽管xsd.exe具备这些功能,但不一定用它为串行化创建类。这个过程是很简单的。下面介绍一个简单的应用程序,它串行化一个类,读取在本章前面保存的产品数据。该代码在SerialSample1文件夹中。示例代码的起始部分比较简单,创建一个新的Product对象pd,并给它填充一些数据:

private void button1_Click(object sender, System.EventArgs e)

{

   //new products object

   Products pd=new Products();

   //set some properties

   pd.ProductID=200;

   pd.CategoryID=100;

   pd.Discontinued=false;

   pd.ProductFTEL="Serialize Objects";

   pd.QuantityPerUnit="6";

   pd.ReorderLevel=1;

   pd.SupplierID=1;

   pd.UnitPrice=1000;

   pd.UnitsInStock=10;

   pd.UnitsOnOrder=0;

XmlSerializer类的Serialize方法实际执行串行化,它有6个重载方法。第一个重载方法的参数是要写入数据的流,可以是StreamTextWriterXmlWriter。在本例中,创建了一个基于TextWriter的对象tr。接着创建了基于XmlSerializer的对象srXmlSerializer需要知道要串行化的对象的类型信息,所以对要串行化的类型使用typeof关键字。在创建sr对象后,调用Serialize方法,其参数是tr(基于Stream的对象)和要串行化的对象,在本例中是pd。确保完成后关闭该数据流。

   //new TextWriter and XmlSerializer

   TextWriter tr=new StreamWriter("..\\..\\..\\serialprod.xml");

   XmlSerializer sr=new XmlSerializer(typeof(Products));

   //serialize object

   sr.Serialize(tr,pd);

   tr.Close();

}

下面介绍Products类,即要串行化的类。这个类与以前编写的其他类的惟一区别是给它增加了C#属性。这些属性中的XmlRootAttribute XmlElementAttribute类由System.Attribute类继承而来。不要把这些属性与XML文档中的属性相混淆。C#属性仅是一些声明信息,在运行期间可以由CLR获取(详见第6)。在本例中,添加一些描述如何串行化对象的属性:

//class that will be serialized.

//attributes determine how object is serialized

[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]

public class Products

{

   [System.Xml.Serialization.XmlElementAttribute(IsNullable=false)]

   public int ProductID;

   [System.Xml.Serialization.XmlElementAttribute(IsNullable=false)]

   public string ProductName;

   [System.Xml.Serialization.XmlElementAttribute()]

   public int SupplierID;

   [System.Xml.Serialization.XmlElementAttribute()]

   public int CategoryID;

   [System.Xml.Serialization.XmlElementAttribute()]

   public string QuantityPerUnit;

   [System.Xml.Serialization.XmlElementAttribute()]

   public System.Decimal UnitPrice;

   [System.Xml.Serialization.XmlElementAttribute()]

   public short UnitsInStock;

   [System.Xml.Serialization.XmlElementAttribute()]

   public short UnitsOnOrder;

   [System.Xml.Serialization.XmlElementAttribute()]

   public short ReorderLevel;

   [System.Xml.Serialization.XmlElementAttribute()]

   public bool Discontinued;

}

Products类定义前面的属性中调用的XmlRootAttribute()把这个类标识为根元素(XML文件中,由串行化生成的)。包含XmlElementAttribute()的属性把该属性下面的成员看做为表示一个XML元素。

查看一下刚才在串行化过程中创建的XML文档,就会发现它与前面创建的其他XML文档非常类似。这就是本练习的目的。下面就是这个文档:

<?xml version="1.0" encoding="utf-8"?>

<Products xmlns:xsd="http://www.w3.org/2001/XMLSchema"

          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

   <ProductID>200</ProductID>

   <ProductName>Serialize Objects</ProductName>

   <SupplierID>1</SupplierID>

   <CategoryID>100</CategoryID>

   <QuantityPerUnit>6</QuantityPerUnit>

   <UnitPrice>1000</UnitPrice>

   <UnitsInStock>10</UnitsInStock>

   <UnitsOnOrder>0</UnitsOnOrder>

   <ReorderLevel>1</ReorderLevel>

   <Discontinued>false</Discontinued>

</Products>

这里没有任何不寻常的地方。可以以XML文档的任何方式来使用这个文档。可以对它进行转换,以HTML格式显示它,使用ADO.NET将其加载到DataSet中,用它加载XmlDocument,或者像在该示例中那样,对它进行反串行化,创建一个对象,该对象的状态与串行化前pd的状态一样(这就是第二个按钮的作用)

接着添加另一个按钮事件处理程序,反串行化一个基于Products的新对象newPd。这次使用FileStream对象读取XML

private void button2_Click(object sender, System.EventArgs e)

{

   //create a reference to products type

   Products newPd;

   //new filestream to open serialized object

   FileStream f=new FileStream("..\\..\\..\\serialprod.xml",FileMode.Open);

再传入Product的类型信息,创建一个新XmlSerializer。然后就可以调用Deserialize()方法。注意在创建newPd对象时,仍需要进行显式的类型转换。此时newPdpd的状态完全一样:

   //new serializer

   XmlSerializer newSr=new XmlSerializer(typeof(Products));

   //deserialize the object

   newPd=(Products)newSr.Deserialize(f);

   //load it in the list box.

   listBox1.Items.Add(newPd.ProductName);

   f.Close();

}

这个示例比较简单。下面介绍一个使用XmlSerializer类的略微复杂的示例。这个示例使每个字段变成private,只能通过Product类中的getset方法来访问,再给XML文件添加一个Discount属性,以说明属性是如何串行化的。

该示例在SerialSample2中,下面是新的Products类:

[System.Xml.Serialization.XmlRootAttribute()]

public class Products

{

   private int prodId;

   private string prodName;

   private int suppId;

   private int catId;

   private string qtyPerUnit;

   private Decimal unitPrice;

   private short unitsInStock;

   private short unitsOnOrder;

   private short reorderLvl;

   private bool discont;

   private int disc;

 

   //add the Discount attribute

   [XmlAttributeAttribute(AttributeFTEL="Discount")]

   public int Discount

   {

      get {return disc;}

      set

   }

   [XmlElementAttribute()]

   public int ProductID

   {

      get {return prodId;}

      set

   }

   ...

   // properties for most of the fields are not shown for sake of brevity

   ...

   [XmlElementAttribute()]

   public bool Discontinued

   {

      get {return discont;}

      set

   }

}

还需要对按钮单击事件的处理程序作如下修改:

private void button1_Click(object sender, System.EventArgs e)

{

   //new products object

   Products pd=new Products();

   //set some properties

   pd.ProductID=200;

   pd.CategoryID=100;

   pd.Discontinued=false;

   pd.ProductFTEL="Serialize Objects";

   pd.QuantityPerUnit="6";

   pd.ReorderLevel=1;

   pd.SupplierID=1;

   pd.UnitPrice=1000;

   pd.UnitsInStock=10;

   pd.UnitsOnOrder=0;

   pd.Discount=2;

   //new TextWriter and XmlSerializer

   TextWriter tr=new StreamWriter("..\\..\\..\\serialprod1.xml");

   XmlSerializer sr=new XmlSerializer(typeof(Products));

   //serialize object

   sr.Serialize(tr,pd);

   tr.Close();

}

private void button2_Click(object sender, System.EventArgs e)

{

   //create a reference to products type

   Products newPd;

   //new filestream to open serialized object

   FileStream f=new FileStream("..\\..\\..\\serialprod1.xml",FileMode.Open);

   //new serializer

   XmlSerializer newSr=new XmlSerializer(typeof(Products));

   //deserialize the object

   newPd=(Products)newSr.Deserialize(f);

   //load it in the list box.

   listBox1.Items.Add(newPd.ProductName);

   f.Close();

}

运行这段代码,得到的结果与前面一样,但有一个区别。输出结果(serialprod1.xml)如下所示:

<?xml version="1.0" encoding="utf-8"?>

<Products xmlns:xsd="http://www.w3.org/2001/XMLSchema"

          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

          Discount="2">

   <ProductID>200</ProductID>

   <ProductName>Serialize Objects</ProductName>

   <SupplierID>1</SupplierID>

   <CategoryID>100</CategoryID>

   <QuantityPerUnit>6</QuantityPerUnit>

   <UnitPrice>1000</UnitPrice>

   <UnitsInStock>10</UnitsInStock>

   <UnitsOnOrder>0</UnitsOnOrder>

   <ReorderLevel>1</ReorderLevel>

   <Discontinued>false</Discontinued>

</Products>

注意Products元素上的Discount属性。既然已经定义了该属性的访问器,就可以在属性中添加更复杂的有效性验证代码。

如果有派生的类和可能返回一个数组的属性,也可以使用XmlSerializer。下面介绍一个解决这些问题的复杂示例。

首先定义3个类ProductBookProduct (派生于Product)Inventory (包含另外两个类):

public class Product

{

   private int prodId;

   private string prodName;

   private int suppId;

   public Product() {}

   public int  ProductID

   {

      get {return prodId;}

      set

   }

   public string ProductName

   {

      get {return prodName;}

      set

   }

   public int SupplierID 

   {

      get {return suppId;}

      set

   }

}

 

public class BookProduct : Product

{

   private string isbnNum;

   public BookProduct() {}

   public string ISBN 

   {

      get {return isbnNum;}

      set

   }

}

 

public class Inventory

{

   private Product[] stuff;

   public Inventory() {}

   //need to have an attribute entry for each data type

   [XmlArrayItem("Prod",typeof(Product)),

   XmlArrayItem("Book",typeof(BookProduct))]

   public Product[] InventoryItems

   {

      get {return stuff;}

      set

   }

}

在此我们只对Inventory类感兴趣。如果串行化这个类,就需要插入一个属性,该属性为每个要添加到数组中的类型包含一个XmlArrayItem构造函数。注意,XmlArrayItem是由XmlArrayItemAttribute类表示的.NET属性名。

这些构造函数的第一个参数是在串行化过程中创建的XML文档中的元素名。如果不使用ElementName参数,元素的名称就会与对象类型名相同(在本例中,就是Product BookProduct)。必须指定的第二个参数是对象的类型。

如果属性返回一个对象数组或基本类型的数组,还要使用XmlArrayAttribute类。因为要在数组中返回不同的类型,所以使用XmlArrayItemAttribute,它允许进行更高级别的控制。

button1_Click事件处理程序中,创建一个新的Product对象和一个新的BookProduct对象(newProd newBook)。给每个对象的各种属性添加数据,再把这些对象添加到一个Product数组中。把这个数组作为参数,创建一个新的Inventory对象, 然后串行化Inventory对象,以便在以后重新创建它:

 

 

private void button1_Click(object sender, System.EventArgs e)

{

   //create new book and bookproducts objects

   Product newProd=new Product();

   BookProduct newBook=new BookProduct();

   //set some properties

   newProd.ProductID=100;

   newProd.ProductFTEL="Product Thing";

   newProd.SupplierID=10;

   newBook.ProductID=101;

   newBook.ProductFTEL="How to Use Your New Product Thing";

   newBook.SupplierID=10;

   newBook.ISBN="123456789";

   //add the items to an array

   Product[] addProd=;

   //new inventory object using the addProd array

   Inventory inv=new Inventory();

   inv.InventoryItems=addProd;

   //serialize the Inventory object

   TextWriter tr=new StreamWriter("..\\..\\..\\order.xml");

   XmlSerializer sr=new XmlSerializer(typeof(Inventory));

   sr.Serialize(tr,inv);

   tr.Close();

}

下面是XML文档的内容(其代码包含在SerialSample3文件夹中)

<?xml version="1.0" encoding="utf-8"?>

<Inventory xmlns:xsd="http://www.w3.org/2001/XMLSchema">

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

   <InventoryItems>

      <Prod>

         <ProductID>100</ProductID>

         <ProductName>Product Thing</ProductName>

         <SupplierID>10</SupplierID>

      </Prod>

      <Book>

         <ProductID>101</ProductID>

         <ProductName>How to Use Your New Product Thing</ProductName>

         <SupplierID>10</SupplierID>

         <ISBN>123456789</ISBN>

      </Book>

   </InventoryItems>

</Inventory>

注意button2_Click事件处理程序执行Inventory对象的反串行化。注意在新建的newInv对象中,我们迭代了数组,以说明其数据保持不变:

private void button2_Click(object sender, System.EventArgs e)

{

   Inventory newInv;

   FileStream f=new FileStream("..\\..\\..\\order.xml",FileMode.Open);

   XmlSerializer newSr=new XmlSerializer(typeof(Inventory));

   newInv=(Inventory)newSr.Deserialize(f);

   foreach(Product prod in newInv.InventoryItems)

      listBox1.Items.Add(prod.ProductName);

   f.Close();

}

不能访问源代码的串行化

这些代码都很好地发挥了作用,但如果不能访问已经串行化的类型的源代码,该怎么办?如果没有源代码,就不能添加属性。此时可以采用另一种方式。可以使用XmlAttributes类和XmlAttributeOverrides类,这两个类可以完成刚才的任务,但不需要添加属性。下面的代码说明了这两个类的工作方式,这段代码在SerialSample4文件夹中。

对于这个示例,假定InventoryProduct和派生的BookProduct类在一个单独的DLL中,而且没有源代码。ProductBookProduct类与前面的示例相同,但应注意Inventory类中没有添加属性:

public class Inventory

{

   private Product[] stuff;

   public Inventory() {}

   public Product[] InventoryItems

   {

      get {return stuff;}

      set

   }

}

下面处理button1_Click()事件处理程序中的串行化:

private void button1_Click(object sender, System.EventArgs e)

{

串行化过程的第一步是创建一个XmlAttributes对象,为每个要重写的数据类型创建一个XmlElementAttribute对象:

   XmlAttributes attrs=new XmlAttributes();

   attrs.XmlElements.Add(new XmlElementAttribute("Book",typeof(BookProduct)));

   attrs.XmlElements.Add(new XmlElementAttribute("Product",typeof(Product)));

从中可以看出,我们给XmlAttributes类的 XmlElements集合添加了一个新的XmlElementAttributeXmlAttributes类的属性对应于可以应用的属性,前面示例中的XmlArray XmlArrayItems仅是其中的几个属性而已。现在我们有一个XmlAttributes对象,并在XmlElements集合中添加了两个基于XmlElementAttribute的对象。

接着创建XmlAttributeOverrides对象:

   XmlAttributeOverrides attrOver=new XmlAttributeOverrides();

   attrOver.Add(typeof(Inventory),"InventoryItems",attrs);

这个类的Add方法有两个重载方法。第一个重载方法的参数是要重写的对象的类型信息和前面创建的XmlAttributes对象,本例中使用了第二个重载方法,其参数也是一个字符串值,该字符串是重写对象的成员。在本例中,要重写Inventory类中的InventoryItems成员。

下面把添加的XmlAttributeOverrides对象作为参数,创建XmlSerializer对象。现在XmlSerializer知道我们要重写的类和需要为这些类型返回的内容。

   //create the Product and Book objects

   Product newProd=new Product();

   BookProduct newBook=new BookProduct();

   newProd.ProductID=100;

   newProd.ProductFTEL="Product Thing";

   newProd.SupplierID=10;

   newBook.ProductID=101;

   newBook.ProductFTEL="How to Use Your New Product Thing";

   newBook.SupplierID=10;

   newBook.ISBN="123456789";

   Product[] addProd=;

  

   Inventory inv=new Inventory();

   inv.InventoryItems=addProd;

   TextWriter tr=new StreamWriter("..\\..\\..\\inventory.xml");

   XmlSerializer sr=new XmlSerializer(typeof(Inventory),attrOver);

   sr.Serialize(tr,inv);

   tr.Close();

}

如果执行Serialize方法,将得到如下XML输出:

<?xml version="1.0" encoding="utf-8"?>

<Inventory xmlns:xsd="http://www.w3.org/2001/XMLSchema">

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

   <Product>

      <ProductID>100</ProductID>

      <ProductName>Product Thing</ProductName>

      <SupplierID>10</SupplierID>

   </Product>

   <Book>

      <ProductID>101</ProductID>

      <ProductName>How to Use Your New Product Thing</ProductName>

      <SupplierID>10</SupplierID>

      <ISBN>123456789</ISBN>

   </Book>

</Inventory>

可以看出,得到的XML与前面的示例完全相同。为了反串行化对象,重新创建基于Inventory的对象,需要创建在串行化对象时创建的XmlAttributesXmlElementAttribute XmlAttribute Overrides对象。之后就可以读取XML,像以前那样重新创建Inventory对象了。下面的代码反串行化Inventory对象:

private void button2_Click(object sender, System.EventArgs e)

{

   //create the new XmlAttributes collection

   XmlAttributes attrs=new XmlAttributes();

   //add the type information to the elements collection

   attrs.XmlElements.Add(new XmlElementAttribute("Book",typeof(BookProduct)));

   attrs.XmlElements.Add(new XmlElementAttribute("Product",typeof(Product)));

   XmlAttributeOverrides attrOver=new XmlAttributeOverrides();

   //add to the Attributes collection

 

   attrOver.Add(typeof(Inventory),"InventoryItems",attrs);

   //need a new Inventory object to deserialize to   

   Inventory newInv;

   //deserialize and load data into the listbox from deserialized object

   FileStream f=new FileStream("..\\..\\..\\inventory.xml",FileMode.Open);

   XmlSerializer newSr=new XmlSerializer(typeof(Inventory),attrOver);

   newInv=(Inventory)newSr.Deserialize(f);

   if(newInv!=null)

   {

      foreach(Product prod in newInv.InventoryItems)

         listBox1.Items.Add(prod.ProductName);

   }

   f.Close();

}

注意,前几行代码与串行化对象所用的代码相同。

System.Xml.XmlSerialize命名空间提供了一个功能非常强大的工具集,可以把对象串行化到XML中。把对象串行化和反串行化到XML中替代了把对象保存为二进制格式,因此可以通过XML对对象进行其他处理。这将大大增强设计的灵活性。

智动软件

赞助商链接

热门内容

相关内容

联系我们

联系方式