views:

15210

answers:

18

I've run into a few gotchas when doing C# XML serialization that I thought I'd share:


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{      
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();

        if (wasEmpty)
            return;

        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");

            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}

Any other XML Serialization gotchas out there?

A: 

Private variables/properties are not serialized in the default mechanism for XML serialization, but are in binary serialization.

Charles Graham
Yes, if you are using the "default" XML serialization.You can specify custom XML serialization logic implementing IXmlSerializable in your class and serialize any private fields you need/want.
Yacoder
Well, this is true. I will edit this. But implementing that interface is sort of a pain in the ass from what I remember.
Charles Graham
+1  A: 

Oh here's a good one: since the XML serialization code is generated and placed in a separate DLL, you don't get any meaningful error when there is a mistake in your code that breaks the serializer. Just something like "unable to locate s3d3fsdf.dll". Nice.

Eric Z Beard
You can generate that DLL ahead of time by using XML "Serializer Generator Tool (Sgen.exe)" and deploy with your application.
huseyint
A: 

Private variables/properties are not serialized in XML serialization, but are in binary serialization.

I believe this also gets you if you are exposing the private members through public properties - the private members don't get serialised so the public members are all referencing null values.

Dr8k
This is not true. The setter of the public property would be called, and would, presumably, set the private member.
John Saunders
+8  A: 

I can't make comments yet, so I will comment on Dr8k's post and make another observation. Private variables that are exposed as public getter/setter properties, and do get serialized/deserialized as such through those properties. We did it at my old job al the time.

One thing to note though is that if you have any logic in those properties, the logic is run, so sometimes, the order of serialization actually matters. The members are implicitly ordered by how they are ordered in the code, but there are no guarantees, especially when you are inheriting another object. Explicitly ordering them is a pain in the rear.

I've been burnt by this in the past.

Charles Graham
I found this post while searching for ways to explicitly set the order of fields.This is done with attributes:[XmlElementAttribute(Order = 1)] public int Field {...}Downside: the attribute must be specified for ALL fields in the class and all its descendents!IMO You should add this to your post.
Cristi Diaconescu
+2  A: 

IEnumerables that are generated via yield returns are not serializable. This is because the compiler generates a separate class to implement yield return and that class is not marked as serializable.

This applies to the 'other' serialization, i.e. the [Serializable] attribute.This doesn't work for XmlSerializer either, though.
Tim Robinson
+1  A: 

If your XML Serialization generated assembly is not in the same Load context as the code attempting to use it, you will run into awesome errors like:

System.InvalidOperationException: There was an error generating the XML document.
---System.InvalidCastException: Unable to cast object
of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at
Microsoft.Xml.Serialization.GeneratedAssembly.
  XmlSerializationWriterSettings.Write3_Settings(Object o)

The cause of this for me was a plugin loaded using LoadFrom context which has many disadvantages to using the Load context. Quite a bit of fun tracking that one down.

sixlettervariables
+6  A: 

Another huge gotcha: when outputting XML through a web page (ASP.NET), you don't want to include the Unicode Byte-Order Mark. Of course, the ways to use or not use the BOM are almost the same:

http://today.icantfocus.com/blog/microshaft/troubleshooting-common-problems-with-the-xmlserializer/

BAD (includes BOM):

XmlTextWriter wr = new XmlTextWriter(stream, New System.Text.Encoding.UTF8);

GOOD:

XmlTextWriter  wr = new XmlTextWriter(stream, New System.Text.UTF8Encoding(false) )

You can explicitly pass false to indicate you don't want the BOM. Notice the clear, obvious difference between Encoding.UTF8 and UTF8Encoding.

kurious
Your comment would be even more useful if you would tell us not just what, but why.
Neil Whitaker
This is not really related to XML serialization... it's just a XmlTextWriter issue
Thomas Levesque
-1: Not related to the question, and you shouldn't be using `XmlTextWriter` in .NET 2.0 or above.
John Saunders
A: 

Serialization has some default rules as peopel have mentioned before... In Binary mode it will serialize everything unless told not too with an attribute, i.e. [NonSerialized].

On the other hand for non binary based serialization, it will only serialize out public properties. This is because when you deserialize the XML, it uses a default constructor to create the object and then calls each public property with it's corresponding XML data node. Also your class must be marked as [Serializable] as well.

That aside, you can use various attributes to control the serialization process...

However, if you need to work with a non-default (empty) constructor or need to serialize out non public information... You will either need to change your class or spend alot of time cofiguring advanced properties of the XML serializer... See the MSN web page for the details and FAQ...

http://msdn.microsoft.com/en-us/library/ms950721.aspx

Shire
"Also your class must be marked as [Serializable] as well." : no, this attribute has nothing to do with XML serialization
Thomas Levesque
-1: unrelated to XML Serialization.
John Saunders
@Thomas - wow thanks. I just wrote 31 classes for XML serialization and included `[Serializable]` on all of them.
Callum Rogers
I believe Thomas was saying you don't need to include [Serializable] on your classes for XmlSerialization. I needed to do that to get it to work though.
Shire
A: 

I can't really explain this one, but I found this won't serialise:

[XmlElement("item")]
public myClass[] item
{
    get { return this.privateList.ToArray(); }
}

but this will:

[XmlElement("item")]
public List<myClass> item
{
    get { return this.privateList; }
}

And also worth noting that if you're serialising to a memstream, you might want to seek to 0 before you use it.

annakata
I think it's because it can't rebuild it. In the secondle example it can call item.Add() to add items to the List. It can't do it in the first.
ilitirit
Use:[XmlArray("item"), XmlArrayItem("myClass", typeof(myClass))]
PoweRoy
cheers for that! learn something every day
annakata
A: 

If your XSD makes use of substitution groups, then chances are you can't (de)serialize it automatically. You'll need to write your own serializers to handle this scenario.

Eg.

<xs:complexType name="MessageType" abstract="true">
    <xs:attributeGroup ref="commonMessageAttributes"/>
</xs:complexType>

<xs:element name="Message" type="MessageType"/>

<xs:element name="Envelope">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
            <xs:element ref="Message" minOccurs="0" maxOccurs="unbounded"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageA" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageB" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

In this example, an Envelope can contain Messages. However, the .NET's default serializer doesn't distinguish between Message, ExampleMessageA and ExampleMessageB. It will only serialize to and from the base Message class.

ilitirit
A: 

Be careful serialising types without explicit serialisation, it can result in delays while .Net builds them. I discovered this recently while serialising RSAParameters.

Keith
+3  A: 

You can't serialize read-only properties. You must have a getter and a setter, even if you never intend to use deserialization to turn XML into an object.

For the same reason, you can't serialize properties that return interfaces: the deserializer wouldn't know what concrete class to instantiate.

Tim Robinson
Actually you can serialize a collection property even if it has no setter, but it has to be initialized in the constructor so that the deserialization can add items to it
Thomas Levesque
+1  A: 

You may face problems serializing objects of type Color and/or Font.

Here are the advices, that helped me:

http://www.codeproject.com/KB/XML/xmlsettings.aspx

http://www.codeproject.com/KB/cs/GenericXmlSerializition.aspx

Yacoder
+1  A: 

One more thing to note: you can't serialize private/protected class members if you are using the "default" XML serialization.

But you can specify custom XML serialization logic implementing IXmlSerializable in your class and serialize any private fields you need/want.

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx

Yacoder
+1  A: 

When serializing into an XML string from a memory stream, be sure to use MemoryStream#ToArray() instead of MemoryStream#GetBuffer() or you will end up with junk characters that won't deserialize properly (because of the extra buffer allocated).

realgt
-1: This sounds like nonsense. If it's true, then give an example.
John Saunders
straight from the docs "Note that the buffer contains allocated bytes which might be unused. For example, if the string "test" is written into the MemoryStream object, the length of the buffer returned from GetBuffer is 256, not 4, with 252 bytes unused. To obtain only the data in the buffer, use the ToArray method; however, ToArray creates a copy of the data in memory."http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx
realgt
+1  A: 

Can't serialize an object which doesn't have a parameterless construtor (just got bitten by that one).

And for some reason, from the following properties, Value gets serialised, but not FullName:

    public string FullName { get; set; }
    public double Value { get; set; }

I never got round to working out why, I just changed Value to internal...

Benjol
The parameterless constructor can be private/protected. It will be enough for XML serializer. The issue with FullName is really strange, shouldn't happen...
Yacoder
A: 

Just FYI, there is a stranded "}" outside of the code-snippet in the original post. I don't have the rep to make changes otherwise I'd correct it myself. ;-)

Jon
Thanks, I just updated it.
kurious
+2  A: 

If the serializer encounters a member/property that has an interface as its type, it won't serialize. For example, the following won't serialize to XML:

public class ValuePair
{
    public ICompareable Value1 { get; set; }
    public ICompareable Value2 { get; set; }
}

Though this will serialize:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek