Introduction
In the final part in this series on serialization, we are going to take a look at XML serialization.
Now unlike binary and SOAP serialization, which we looked at in parts I and II respectively, XML serialization requires the use of a completely different framework.
The Math Game
For this article, we are going to use the example of a simple math game. The user is presented with a random sum and they have to provide the answer, gaining two points for each correct answer and losing 1 point for each incorrect answer. The application keeps the score and allows the user to save a game in progress to continue it at a later point. The class diagram is shown below (click to zoom):
Making Objects Serializable
Now, unlike binary and SOAP serialization where objects are non-serializable unless explicitly stated otherwise through use of the Serializable
attribute; with XML serialization all objects are implicitly serializable and do not require the use of such an attribute. That said, as you will see below, we still end up liberally decorating our classes with various different attributes to further refine the serialization process.
Firstly, let's take a look at the Question
class:
// C#
[XmlRoot(ElementName = "question")]
public class Question
{
[XmlAttribute(AttributeName = "left")]
public int LeftOperand { get; set; }
[XmlAttribute(AttributeName = "right")]
public int RightOperand { get; set; }
[XmlAttribute(AttributeName = "operator")]
public Operator Operator { get; set; }
}
' Visual Basic
<XmlRoot(ElementName:="question")>
Public Class Question
<XmlAttribute(AttributeName:="left")>
Public Property LeftOperand As Integer
<XmlAttribute(AttributeName:="right")>
Public Property RightOperand As Integer
<XmlAttribute(AttributeName:="operator")>
Public Property [Operator] As [Operator]
End Class
Note how the class is decorated with an XmlRoot
attribute. This attribute controls how an object of this class should be serialized if it is the root element of the XML document. In this case we use it to make sure the element is rendered in lower case.
Next, notice the three properties of the class are decorated with XmlAttribute
attributes. By default the XML serializer serializes all properties as XML elements. By using this attribute, we override this behaviour, serializing the properties as attributes instead. At the same time, we are also changing the attribute name to something more concise.
Next we will take a look at the UserAnswer
class:
// C#
[XmlRoot(ElementName = "answer")]
public class UserAnswer
{
[XmlElement(ElementName = "question")]
public Question Question { get; set; }
[XmlElement(ElementName = "value")]
public int Answer { get; set; }
public bool IsCorrect
{
get { return Answer == Question.CorrectAnswer; }
}
}
' Visual Basic
<XmlRoot(ElementName:="answer")>
Public Class UserAnswer
<XmlElement(ElementName:="question")>
Public Property Question As Question
<XmlElement(ElementName:="value")>
Public Property Answer As Integer
Public ReadOnly Property IsCorrect As Boolean
Get
Return Answer = Question.CorrectAnswer
End Get
End Property
End Class
Here we are using the XmlElement
attribute to override the default names given to the XML elements upon serialization. By default, the serializer will name any XML elements or attributes exactly as the corresponding property is named, however in our example, we want the elements in lower case.
It is also worth noting that because the IsCorrect
property is read-only, it will not be serialized. However, if the property was not read-only and we didn't want it serialized, we would simply decorated it with the XmlIgnore
attribute.
Now finally, the Game
class:
// C#
[XmlRoot(ElementName = "game")]
public class Game
{
[XmlArray(ElementName = "answers")]
[XmlArrayItem(ElementName = "answer")]
public UserAnswersCollection Answers { get; set; }
public int Score
{
get { return (Answers.Count(x => x.IsCorrect) * 2) - Answers.Count(x => !x.IsCorrect); }
}
}
' Visual Basic
<XmlRoot(ElementName:="game")>
Public Class Game
<XmlArray(ElementName:="answers")>
<XmlArrayItem(ElementName:="answer")>
Public Property Answers As UserAnswersCollection
Public ReadOnly Property Score As Integer
Get
Return (Answers.Where(Function(x) x.IsCorrect).Count() * 2) - Answers.Where(Function(x) Not x.IsCorrect).Count()
End Get
End Property
End Class
Note how the Answers
property is decorated with both an XmlArray
and XmlArrayItem
attribute. This specifies that the Answers
collection is to be serialized as an array with an element name of "answers". Each answer in the collection will be serialized as an individual element named "answer".
OK, Let's Start Serializing
In order to serialize and de-serialize our objects, we need to use an XmlSerializer
object. The example below shows how to use the XmlSerializer
to save the current game:
// C#
public void SaveGame(Game game, string fileName)
{
using (Stream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
XmlSerializer serializer = new XmlSerializer(typeof(Game));
serializer.Serialize(fileStream, game);
}
}
' Visual Basic
Public Sub SaveGame(ByVal game As Game, ByVal fileName As String)
Using fileStream As Stream = New FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)
Dim serializer As XmlSerializer = New XmlSerializer(GetType(Game))
serializer.Serialize(fileStream, game)
End Using
End Sub
As with binary and SOAP serialization, we can serialize to any object which inherits from the Stream
class. In our example, we use a FileStream
object, but this could just as easily have been a NetworkStream
object.
Finally, for completeness, the code for loading a previously-saved game from disk
// C#
public Game LoadGame(string fileName)
{
using (Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlSerializer deserializer = new XmlSerializer(typeof(Game));
return (Game)deserializer.Deserialize(fileStream);
}
}
' Visual Basic
Public Function LoadGame(ByVal fileName As String) As Game
Using fileStream As Stream = New FileStream(fileName, FileMode.Create, FileAccess.Read, FileShare.Read)
Dim deserializer As XmlSerializer = New XmlSerializer(GetType(Game))
Return DirectCast(deserializer.Deserialize(fileStream), Game)
End Using
End Function
Below is an example of the XML produced when serializing a game:
<?xml version="1.0"?>
<game xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<answers>
<answer>
<question left="6" right="6" operator="Addition" />
<value>12</value>
</answer>
<answer>
<question left="8" right="2" operator="Multiplication" />
<value>16</value>
</answer>
<answer>
<question left="8" right="8" operator="Division" />
<value>1</value>
</answer>
<answer>
<question left="1" right="3" operator="Multiplication" />
<value>4</value>
</answer>
</answers>
</game>
A Quick Note on SOAP
As the SOAP message format is based on XML, you can use XML serialization for serializing and de-serializing objects to and from SOAP messages. Indeed, this method is now preferred over using the SoapFormatter
discussed in the previous article. You can find further details on this in the MSDN documentation.
Summary
XML serialization provides the means to serialize objects in a human-readable form and uses a completely separate framework from binary and SOAP serialization. It is also the preferred method for serializing objects to SOAP messages.
You can download the source code for the math game here.
No comments:
Post a Comment