George V. Reilly

Serializing a NameValueCollection

Serializing a NameValueCollection

I had a NameVal­ueCol­lec­tion embedded inside a larger object. I needed to serialize the larger object into XML and back. Un­for­tu­nate­ly, NameVal­ueCol­lec­tion is not XML se­ri­al­iz­able. Why I do not know.

A blog comment from Tim Erwin got me started in the right direction. Implement IXmlSe­ri­al­iz­able and do the work by hand in ReadXml and WriteXml.

Tim’s im­ple­men­ta­tion 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 de­se­ri­al­iza­tion 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 GetH­ash­Code, as the NameVal­ueCol­lec­tion im­ple­men­ta­tions 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;
}
blog comments powered by Disqus
Review: The Accusers » « Review: The Belisarius Series