Introduction
This article discusses how to serialize classes that usually cannot be serialized with XmlSerializer
, by implementing IXmlSerializer
interface. This article will show 3 different implementations.
Background
I usually have to serialize object into XML files, to save the config and what you can imagine. I was tired of XmlSerialization
limits, so was everybody, and I searched CodeProject to find out how I can do it. There are a lot of articles about those problems. You can serialize Hashtable
using binary, but this is not XML. You can serialize all that you want by manually doing the serialization element by element, but if you have to do this for big or complex classes, it could be a lot of work.
Finally, I discovered the IXmlSerializable
interface. It allows to modify the behavior of the XmlSerializer
. So far, I found nothing about this interface on CodeProject, which is my reference. So, I decided to write this article. In this article, I will talk only about the XmlSerializer
, and how IXmlSerializable
interface can be used through it.
IXmlSerializer
When you serialize an object using XmlSerializer
, there are two cases, first (general case) the object does not implement the IXmlSerializable
interface, and the XmlSerializer
uses Reflection to write or read XML files. In the second case, your object implements IXmlSerializable
and instead of Reflection, the IXmlSerializable.WriteXml
and IXmlSerializable.ReeadXml
methods are called:
public interface IXmlSerializable
{
XmlSchema GetSchema();
void ReadXml(XmlReader reader);
void WriteXml(XmlWriter writer);
}
First Implementation Example: HastableSerializable
This example is from Matt Berther. It is the best and simplest example that I have found, so thank you Matt. Here is the code class, it is really simple.
public class HashtableSerializable : Hashtable, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema(){ ... }
public void ReadXml(System.Xml.XmlReader reader){ ... }
public void WriteXml(System.Xml.XmlWriter writer){ ... }
}
I do not need to get a schema, this article is not about this implementation. Look at the WriteXml
method:
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("dictionary");
foreach(object key in this.Keys)
{
object value = this[key];
writer.WriteStartElement("item");
writer.WriteElementString("key", key.ToString());
writer.WriteElementString("value", value.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
}
}
We just write XML like this one:
<dictionary>
<item>
<key>...</key>
<value>...</value>
</item>
</dictionary>
Now we have to re-read it. Please now take a look at the ReadXml
method. When the calling XmlSerialize
calls it, the reader parameter has its position just at the right place for us. Be careful to place it at the right place before returning so the XmlSerializer
can continue the deserialization.
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
reader.ReadStartElement("dictionary");
while(reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
string key = reader.ReadElementString("key");
string value = reader.ReadElementString("value");
reader.ReadEndElement();
reader.MoveToContent();
this.Add(key, value);
}
reader.ReadEndElement();
}
Please pay particular attention to the last reader.ReadEndElement()
, which consumes the last </dictionary>
, and place the reader position at the end.
That's it. Now if I need to serialize a Hashtable
, member of my Class1
, I just have to use a HashtableSerializable
instead.
Second Implementation Example: Override the XmlSerialiser, When It's Already Working
Historically, I have a ByteArray
class that allows me to manipulate bytes. This is an old class from Framework 1.1 and it implements CollectionBase
(It compiles in framework 2). This class implements Icollection
, IEnumerable
, and can be implicitly converted from and into byte[]
. So it is serializable using XmlSerializable
, with this result:
<ArrayOfUnsignedByte
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<unsignedByte>1</unsignedByte>
<unsignedByte>2</unsignedByte>
<unsignedByte>3</unsignedByte>
<unsignedByte>4</unsignedByte>
<unsignedByte>5</unsignedByte>
<unsignedByte>6</unsignedByte>
<unsignedByte>7</unsignedByte>
<unsignedByte>8</unsignedByte>
<unsignedByte>9</unsignedByte>
<unsignedByte>10</unsignedByte>
<unsignedByte>11</unsignedByte>
<unsignedByte>12</unsignedByte>
<unsignedByte>13</unsignedByte>
<unsignedByte>14</unsignedByte>
<unsignedByte>15</unsignedByte>
</ArrayOfUnsignedByte>
Good, but I want to get a more readable result, and written in hexadecimal. So guess what? I implement IXmlSerializable
!
(ByteArray.Parse(string hexa)
parses a string
like 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
, and converts it in a ByteArray
, and ByteArray.ToHexString()
serializes it into the string
.)
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
string temp = reader.ReadString();
try
{
this.Add(ByteArray.Parse(temp));
}
catch{}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteString(this.ToHexString());
}
Now I serialize that way :
="1.0"="utf-8"
<ByteArray>01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F</ByteArray>
Third Implementation Example: Generics
First, usually I use a lot of Lists<>
in my classes, and it serializes very well. Of course, it does not serialize if the objects from the list are not serializable. For this example, I have a Set<T>
class created using existing CodeProject Set
articles:
- Yet Another C# Set Class by Theo Bebekis
- A Set Class by PIEBALDconsult
- A C# Set Class based on enums by ‘RalfW’
- Pascal Set by Scott Mitchell
- Add Support for ‘Set’ Collections to .NET by Jason Smith
- Set Collections for C by Keith Barrett
My class is a mix of those ones. It is constructed around a Hashtable
, and was not serializable, and it was a real problem for me. It is this class which leads me to IXmlSerializable
, and to this article finally. I will not discuss about the Set
class itself, please see previous articles, I do not create anything with that. Please note that it is a generic class. I just add IXmlSerializable
implementation:
public class Set<T> : ICollection<T>, IEnumerable, IXmlSerializable
, IListSource
{
void IXmlSerializable.ReadXml(XmlReader reader)
{
System.Xml.Serialization.XmlSerializer S =
new System.Xml.Serialization.XmlSerializer(typeof(T));
reader.Read();
while(reader.NodeType != XmlNodeType.EndElement)
{
try
{
T item = (T)S.Deserialize(reader);
if(item != null)
this.Add(item);
}
catch
{
}
}
reader.ReadEndElement();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
System.Xml.Serialization.XmlSerializer S =
new System.Xml.Serialization.XmlSerializer(typeof(T));
foreach(T item in this.InternalHashtable.Values)
{
try
{
S.Serialize(writer, item, null);
}
catch
{
}
}
}
}
As you can see, the implementation is strange, because it uses also an XmlSerializer
(don't forget that it is an XmlSerializer
that calls the two methods to read and write XML.)
Points of Interest
Something seems to be missing in my ByteArray
class, I didn't succeed in indenting my data correctly when I have a lot of bytes. I always get only a line using this method. I succeeded in doing many lines, but the indentation is always bad. Please show me how to do it ...
History
- 18th June, 2008: First release
There might be newer versions in the future ...