Serializing a NameValueCollection
I had a NameValueCollection embedded inside a larger object. I needed to serialize the larger object into XML and back. Unfortunately, NameValueCollection is not XML serializable. Why I do not know.
A blog comment from Tim Erwin got me started in the right direction. Implement IXmlSerializable and do the work by hand in ReadXml and WriteXml.
Tim’s implementation turned out to be overly simple. It didn’t handle an empty collection well, nor did it leave the XmlReader in a good state.
I used SGen to examine the deserialization of a List<String> to figure out what else needed to be done.
The following ReadXml seems to work. If I expected to receive XML from untrusted sources, I would make this more robust.
public void ReadXml(XmlReader reader) { if (reader.IsEmptyElement) return; while (reader.Read() && reader.NodeType != XmlNodeType.EndElement && reader.NodeType != XmlNodeType.None) { if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "Header") { reader.MoveToAttribute("name"); string name = reader.Value; reader.MoveToAttribute("value"); string value = reader.Value; Add(name, value); } } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { foreach (string name in nvc.Keys) { writer.WriteStartElement("Header"); string value = nvc[name]; writer.WriteAttributeString("name", name); writer.WriteAttributeString("value", value); writer.WriteEndElement(); } } public XmlSchema GetSchema( ) { return null; }
I also found that I needed to implement custom Equals and GetHashCode, as the NameValueCollection implementations didn’t seem to do what I wanted.
// Have to override GetHashCode() as two apparently identical NameValueCollections // will have different hash codes. public override int GetHashCode() { int hash = nvc.Count; foreach (string name in nvc) { hash = 757 * hash + 101 * nvc[name].GetHashCode() + name.GetHashCode(); } return hash; } public bool Equals(HeadersCollection that) { if (ReferenceEquals(that, null)) return false; if (ReferenceEquals(this, that)) return true; // Have to explicitly compare the contents of the collections // as NameValueCollection.Equals doesn't seem to do what we want. // Note: this is independent of order. if (nvc.Count != that.nvc.Count) return false; foreach (string name in nvc) { if (nvc[name] != that.nvc.Get(name)) return false; } return true; } public static bool Equals(HeadersCollection headersA, HeadersCollection headersB) { if (headersA == null) return (headersB == null); if (ReferenceEquals(headersA, headersB)) return true; return headersA.Equals(headersB); } public override bool Equals(object obj) { if (obj is HeadersCollection) return Equals((HeadersCollection) obj); return false; }