One solution is to implement the IXmlSerializable interface, which exposes the ReadXml(XmlReader) and WriteXml(XmlWriter) operations. Though, this ends in quiet a bunch of complicated code.
To handle the memory problem when using the XmlSerializer class, here you can find two simple wrapper classes which provide a streaming functionality.
XmlStreamingSerializer
The XmlStreamingSerializer creates an internal instance of a XmlSerializer and a XmlWriter which provides the persistence management. To avoid the "xsi" and "xsd" namespaces again and again for each object to be serialized, it simply provides an empty XmlSerializerNamespaces. I found that trick in one of the comments on Scott Hanselman's blog about XmlFragmentWriter.public class XmlStreamingSerializer<T> {
// ------------------------------------------------------------------
static XmlStreamingSerializer() {
_ns = new XmlSerializerNamespaces();
_ns.Add("", "");
}
// ------------------------------------------------------------------
private XmlStreamingSerializer() {
_serializer = new XmlSerializer(typeof(T));
}
// ------------------------------------------------------------------
public XmlStreamingSerializer(TextWriter w)
: this(XmlWriter.Create(w)) {
}
// ------------------------------------------------------------------
public XmlStreamingSerializer(XmlWriter writer) : this() {
_writer = writer;
_writer.WriteStartDocument();
_writer.WriteStartElement("ArrayOf" + typeof(T).Name);
}
// ==================================================================
static XmlSerializerNamespaces _ns;
XmlSerializer _serializer = new XmlSerializer(typeof(T));
XmlWriter _writer;
bool _finished;
// ==================================================================
public void Finish() {
_writer.WriteEndDocument();
_writer.Flush();
_finished = true;
}
// ------------------------------------------------------------------
public void Close() {
if (!_finished)
Finish();
_writer.Close();
}
// ------------------------------------------------------------------
public void Serialize(T item) {
_serializer.Serialize(_writer, item, _ns);
}
}
XmlStreamingDeserializer
As the serializer, the XmlStreamingDeserializer class wraps an instance of a .NET XmlSerializer. It uses a XmlReader to provide the streaming functionality and utilizes the XmlReader.ReadSubtree method to get the current serialized item into the XmlSerializer.public class XmlStreamingDeserializer<T> {
// ------------------------------------------------------------------
static XmlStreamingDeserializer() {
_ns = new XmlSerializerNamespaces();
_ns.Add("", "");
}
// ------------------------------------------------------------------
private XmlStreamingDeserializer() {
_serializer = new XmlSerializer(typeof(T));
}
public XmlStreamingDeserializer(TextReader reader)
: this(XmlReader.Create(reader)) {
}
public XmlStreamingDeserializer(XmlReader reader) : this() {
_reader = reader;
}
// ==================================================================
static XmlSerializerNamespaces _ns;
XmlSerializer _serializer = new XmlSerializer(typeof(T));
XmlReader _reader;
// ==================================================================
public void Close() {
_reader.Close();
}
// ------------------------------------------------------------------
public T Deserialize() {
while (_reader.Read()) {
if (_reader.NodeType == XmlNodeType.Element
&& _reader.Depth == 1
&& _reader.Name == typeof(T).Name) {
XmlReader reader = _reader.ReadSubtree();
return (T)_serializer.Deserialize(reader);
}
}
return default(T);
}
}
Some Tests
Here you can find some performance test results.I serialized a very simple object structure shown below.
public class Foo {
[XmlAttribute]
public int Id { get; set; }
[XmlAttribute]
public string Bar { get; set; }
public List<foo> SubFoos { get; set; }
}
I serialized 10,000 instances, each with 100 sub items. Maybe you don't have to serialize so many objects, but the depth of serialized objects is often far deeper than two levels.
| Action | Duration (ms) | RAM (MB) |
|---|---|---|
| Serialization with XmlSerializer | 2954 | 134 |
| Serialization with XmlStreamingSerializer | 2391 | 13 |
| De-serialization with XmlSerializer | 3662 | 150 |
| De-serialization with XmlStreamingDeserializer | 2953 | 13 |
Possible Extensions
Both classes, shown above are quiet simple and there are several possible extensions to get them more powerful.- One class for both. If you prefer one serialization class for serialization and de-serialization, feel free to merge both into one. I preferred the two-class solution to keep each of them more pure.
- Configurable document element name. Since now, the serializer creates a hard-coded document element name called "ArrayOfMyType". This behavior matches to the XmlSerializer behavior, when serializing an IEnumerable<out T>. Feel free make this
- Xml Namespaces. The above serialization classes doe not support XML namespaces. If you need namespaces just add something like a public XmlSerializerNamespaces property.
- Different objects. XML files often contain more than one type of objects. Support of different object types could easily be added by a dictionary of serializers.
0 Kommentare:
Post a Comment