XML serialization addresses the requirements mentioned in the previous section. In XML serialization, no assumptions are made about the program that produces the XML or the one that reads it. You may lose some precise CLR type detail, but interoperation with disparate applications is better than with runtime serialization. In order to completely divorce the XML from the CLR, the serialization uses XML Schema datatypes. I mentioned in Table 8-2 that not every XSD datatype has a corresponding CLR datatype. Although each XSD datatype mapped to exactly one CLR datatype, many CLR datatypes could, potentially, each be represented by a number of different XSD datatypes.
The essential point to remember when differentiating runtime serialization from XML serialization is that, in the runtime serialization, the object being serialized actively controls the format of the serialization, whereas in the XML serialization, the object is passively serialized.
The
XmlSerializer
type contains the methods
Serialize( )
and Deserialize(
)
. Any object can be serialized to XML and, by default, all
fields in an object are serialized as elements. Certain attributes
can also be used to decorate existing classes and methods. Table 9-2 shows a complete listing of attributes that
affect XML
serialization.
Attribute name |
Description |
|
Place this attribute on a member whose type or return type is an
array of |
|
Place this attribute on a member whose type or return type is an
array of |
|
Place this attribute on a member that returns an array of objects to produce nested XML elements. |
|
Place this attribute on a member that returns an array of objects to indicate the type of each of the nested XML elements. |
|
Place this attribute on a member to indicate that it is to be serialized as an XML attribute. |
|
Place this attribute on a member to indicate that the type of the data to be serialized is indicated by another member, returning an enumeration. |
|
Place this attribute on a member to indicate that it is to be serialized as an XML element. |
|
Place this attribute on a member of an enumeration to set the name
that |
|
Place this attribute on a member to indicate that it should be ignored for purposes of serialization. |
|
Place this attribute on a member to have
|
|
Place this attribute on a class to indicate that the class should be serialized as the document element. |
|
Place this attribute on a member to indicate that it should be serialized as XML text. |
|
Place this attribute on a class to indicate the name of the type and namespace of the XML element. |
|
Place this attribute on a member to indicate the default value for a member if no value is assigned. |
With these attributes, you can take an
arbitrary C# type and tell XmlSerializer
exactly
how you would like to serialize it to XML.
Example 9-7 shows a new XML format for an instance of the personnel records from Example 9-4.
<?xml version="1.0"?> <personnel> <employee firstname="Niel" middleinitial="M" lastname="Bornstein" hiredate="2001-01-01T00:00:00.0000000-05:00"> <addresses> <address type="Home"> <street>999 Wilford Trace</street> <city>Atlanta</city> <state>Georgia</state> <zip>30037</zip> </address> </addresses> </employee> </personnel>
If you were to just serialize a Personnel
object
to XML, all the data would appear in elements as shown in Example 9-8.
<?xml version="1.0"?> <Personnel xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/ 2001/XMLSchema-instance"> <Employees> <Employee> <FirstName>Niel</FirstName> <MiddleInitial>M</MiddleInitial> <LastName>Bornstein</LastName> <Addresses> <Address> <AddressType>Home</AddressType> <Street> <string>999 Wilford Trace</string> </Street> <City>Atlanta</City> <State>GA</State> <Zip>30037</Zip> </Address> </Addresses> <HireDate>2001-01-01T00:00:00.0000000-05:00</HireDate> </Employee> </Employees> </Personnel>
That’s fine, if you want all your data in elements, but some people prefer a healthy mix of elements and attributes; this element-centric output does not match the format in Example 9-7. In addition, the element names don’t match the format you want.
To generate the XML you want, repeat the code from Example 9-4, with the addition of attributes to control
the serialization. Let me step through the changes in each class.
First, AddressType
doesn’t need
to change at all:
public enum AddressType { Home, Office, Billing, Shipping, Mailing, Day, Evening, FAX }
If you’ll look again at Example 9-7, you’ll see that each state
is actually listed by its full name, not the abbreviation as listed
in the State
enumeration. Here
I’ve added an XmlEnumAttribute
for each state name. Note that I’ve skipped some in
the interest of space:
public enum State { [XmlEnum(Name="Alaska")] AK, [XmlEnum(Name="Alabama")] AL, [XmlEnum(Name="Arkansas")] AR, [XmlEnum(Name="Arizona")] AZ, // ... [XmlEnum(Name="Washington")] WA, [XmlEnum(Name="Wisconsin")] WI, [XmlEnum(Name="West Virginia")] WV, [XmlEnum(Name="Wyoming")] WY }
The
Address
class has one attribute,
type
, and four elements. Here
I’ve added XmlAttributeAttribute
and XmlElementAttribute
, as appropriate. The
AttributeName
and ElementName
fields of each attribute are used to set the names of the XML
attributes and elements, respectively:
public class Address { [XmlAttribute(AttributeName="type")] public AddressType AddressType; [XmlElement(ElementName="street")] public string[ ] Street; [XmlElement(ElementName="city")] public string City; [XmlElement(ElementName="state")] public State State; [XmlElement(ElementName="zip")] public string Zip; }
Similar to Address
, the
TelephoneNumber
class has one attribute and three
elements. Again, I’ve decorated each member with the
appropriate attribute. Note also that here, as in
Address
, I’ve set the names of
the attributes and elements to match the ones in the XML; that is,
they all start with lowercase letters:
public class TelephoneNumber { [XmlAttribute(AttributeName="type")] public AddressType AddressType; [XmlElement(ElementName="areacode")] public string AreaCode; [XmlElement(ElementName="exchange")] public string Exchange; [XmlElement(ElementName="number")] public string Number; }
Now we come to the
meat of the personnel record, the Employee
. This
class has three attributes: firstname
,
middleinitial
, and lastname
,
which I’ve treated with the appropriate attribute.
However, the Employee
class also has two
additional elements, addresses
and
telephones
. These two elements actually contain
nested arrays of elements, so I’ve used the
XmlArray
and XmlArrayItem
attributes to help the serializer figure out what to do with the XML
elements it reads:
public class Employee { [XmlAttribute(AttributeName="firstname")] public string FirstName; [XmlAttribute(AttributeName="middleinitial")] public string MiddleInitial; [XmlAttribute(AttributeName="lastname")] public string LastName; [XmlArray(ElementName="addresses")] [XmlArrayItem(ElementName="address")] public Address [ ] Addresses; [XmlArray(ElementName="telephones")] [XmlArrayItem(ElementName="telephone")] public TelephoneNumber [ ] TelephoneNumbers; [XmlAttribute(AttributeName="hiredate")] public DateTime HireDate; }
Here’s
the document element, personnel
, which is
decorated with XmlRootAttribute
. Although the
Employees
member is an array of
Employee
objects, it is not a nested array, like
addresses
and telephones
. By
adding the XmlElement
attribute directly to the
member, the XmlSerializer
knows that this member
is to be serialized as an array of employee
elements, without a separate top-level element:
[XmlRoot(ElementName="personnel")] public class Personnel { [XmlElement(ElementName="employee")] public Employee [ ] Employees; }
Finally, I’ve made some changes to the
Serializer
class, which I introduced in Example 9-5.
Serializer
’s Main(
)
method still uses the CreatePersonnel(
)
to create some personnel records, but it then
instantiates an XmlSerializer
to deserialize the
objects it created back out to a file:
public class Serializer { public static void Main(string [ ] args) { Personnel personnel = CreatePersonnel( ); XmlSerializer serializer = new XmlSerializer(typeof(Personnel)); using (FileStream stream = File.OpenWrite("Personnel.xml")) { serializer.Serialize(stream,personnel); } } }
Unlike the SoapFormatter
and
BinaryFormatter
, the
XmlSerializer
constructor takes the
Type
of the object being serialized as a
parameter. This is because, unlike the formatters, the serializer is
actually created specifically to handle one particular type of
object.
The XmlSerializer
actually generates and compiles
the source code of a class to serialize the object to XML at runtime.
Because of this, you may notice a slight performance difference the
first time you instantiate an XmlSerializer
for a
particular type during each run of your program.
Deserializing an object from XML is as
simple as calling the
XmlSerializer
’s
Deserialize( )
method:
XmlSerializer serializer = new XmlSerializer(typeof(Personnel)); using (FileStream stream = File.OpenRead("Personnel.xml")) { personnel = (Personnel)serializer.Deserialize(stream); }
This data is being serialized to and
deserialized from files, but it could be any
Stream
. When deserializing from an
XmlReader
, you can ensure that the data stream is
valid for the XmlSerializer
instance
you’re using. The CanDeserialize(
)
method takes an XmlReader
parameter,
and returns a Boolean value indicating whether the
XmlReader
contains data that can be deserialized
by the XmlSerializer
.
This is convenient, because when you’re
deserializing data from a source outside of your control, you
don’t always know what the file contains. The
CanDeserialize( )
method can be used to control
processing when you’re unsure of the XML
stream’s contents.
At runtime,
you can override the attributes that affect serialization with the
XmlAttributeOverrides
class. This class serves as
the container for a collection of XmlAttributes
instances, each one of which holds the overridden attributes for a
particular type. XmlAttributes
has a property for
each type of XML attribute; for example, the
XmlAttributeAttribute
can be set with the
XmlAttribute
property. For those attributes that
can exist in multiples, such as
XmlElementAttribute
, the property returns a
collection of those attributes. For example, the
XmlElements
property returns a
XmlElementAttributes
collection, to which you can
add XmlElementAttribute
instances.
XmlAttributeOverrides
is convenient
if you want to serialize an object for which you
don’t have or can’t alter the
source code. You can customize the serialization in exactly the same
ways as you could by applying the attributes in the source.
I’ve altered the same program we’ve
been using to change the name of the root element from
personnel
to employees
. The new
lines are highlighted:
Personnel personnel = CreatePersonnel( ); XmlAttributeOverrides overrides = new XmlAttributeOverrides( ); XmlAttributes attributes = new XmlAttributes( ); attributes.XmlRoot = new XmlRootAttribute("employees"); overrides.Add(typeof(Personnel), attributes); XmlSerializer serializer = new XmlSerializer(typeof(Personnel), overrides); using (FileStream stream = File.OpenWrite("Personnel.xml")) { serializer.Serialize(stream,personnel); }
18.116.27.178