121

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?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Kalid
  • 22,218
  • 14
  • 44
  • 46
  • Lookin for more gotchas lol, you might be able to help me out: http://stackoverflow.com/questions/2663836/is-there-a-way-to-use-a-dictionary-or-xml-in-the-application-settings – Shimmy Weitzhandler Apr 19 '10 at 22:41
  • 1
    Also, you will want to take a look to Charles Feduke's implementation of serialzable dictionary, he made the xml writer to notice between attributable members to regular members to be serialized by the default serializer: http://www.deploymentzone.com/2008/09/19/idictionarytkeytvalue-ixmlserializable-and-lambdas/ – Shimmy Weitzhandler Apr 19 '10 at 22:45
  • This doesn't seem like it quite catches all of the gotchas. I'm setting the IEqualityComparer in the constructor, but that doesn't get serialized in this code. Any ideas on how to extend this Dictionary to include this bit of information? could that information be handled via the Type object? – ColinCren Feb 03 '11 at 20:00

19 Answers19

27

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:

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.

The three extra BOM Bytes at the beginning are (0xEFBBBF) or (239 187 191).

Reference: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

David d C e Freitas
  • 7,481
  • 4
  • 58
  • 67
Kalid
  • 22,218
  • 14
  • 44
  • 46
22

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
  • 24,293
  • 14
  • 43
  • 56
  • 17
    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. – Cristian Diaconescu Oct 21 '08 at 09:13
15

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).

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx

realgt
  • 1,715
  • 15
  • 12
  • 3
    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 Sep 21 '09 at 14:59
  • only just now saw this. No longer sounds like nonsense. – John Saunders Jun 15 '11 at 13:52
  • Never heard this before,which is helpful on debugging. – Ricky Oct 26 '11 at 09:23
10

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
  • 15,813
  • 6
  • 60
  • 93
9

IEnumerables<T> 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.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • This applies to the 'other' serialization, i.e. the [Serializable] attribute. This doesn't work for XmlSerializer either, though. – Tim Robinson Oct 29 '08 at 23:23
  • 1
    [Note that when a class implements the ICollection interface, only the collection contained by the class is serialized. Any public properties or fields added to the class will not be serialized.](http://msdn.microsoft.com/en-us/library/58a18dwa.aspx). An almighty WTF moment and a waste of a good afternoon. – Hugh W Feb 17 '13 at 00:04
  • Perhaps you mean that some output is generated but it can be considered to be incomplete. I find that a class that implements IEnumerable<> is serializable by XmlSerializer but in my view incomplete. The problem is that the items enumerated become the only content serialized. Other public data members are ignored. The enumeration becomes the be-all-and-end-all of the class. – H2ONaCl Feb 24 '21 at 04:36
8

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
  • 53,480
  • 10
  • 121
  • 138
  • 1
    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 Jun 25 '09 at 23:54
7

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
  • 37,669
  • 27
  • 100
  • 145
  • 11
    You can generate that DLL ahead of time by using XML "Serializer Generator Tool (Sgen.exe)" and deploy with your application. – huseyint Dec 25 '08 at 07:43
6

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
  • 63,995
  • 54
  • 186
  • 268
5

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

Massimiliano
  • 16,770
  • 10
  • 69
  • 112
4

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

Massimiliano
  • 16,770
  • 10
  • 69
  • 112
4

See "Advanced XML Schema Definition Language Attributes Binding Support" for details of what is supported by the XML Serializer, and for details on the way in which the supported XSD features are supported.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
4

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.

user7116
  • 63,008
  • 17
  • 141
  • 172
4

If you try to serialize an array, List<T>, or IEnumerable<T> which contains instances of subclasses of T, you need to use the XmlArrayItemAttribute to list all the subtypes being used. Otherwise you will get an unhelpful System.InvalidOperationException at runtime when you serialize.

Here is part of a full example from the documentation

public class Group
{  
   /* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base 
      type (Employee) and derived type (Manager) into serialized arrays. */

   [XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))]
   public Employee[] Employees;
MarkJ
  • 30,070
  • 5
  • 68
  • 111
3

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

Charles Graham
  • 24,293
  • 14
  • 43
  • 56
  • 2
    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. – Massimiliano Dec 24 '08 at 16:59
  • 1
    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 Dec 25 '08 at 07:32
  • BinaryFormatter.Serialize is now deprecated because of a security risk which is too bad because it seems more trouble free than XmlSerializer. – H2ONaCl Feb 24 '21 at 04:34
3

Properties marked with the Obsolete attribute aren't serialized. I haven't tested with Deprecated attribute but I assume it would act the same way.

James Hulse
  • 1,566
  • 15
  • 32
2

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
  • 74,572
  • 17
  • 113
  • 180
2

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
ilitirit
  • 16,016
  • 18
  • 72
  • 111
2

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

Community
  • 1
  • 1
Keith
  • 150,284
  • 78
  • 298
  • 434
0

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
  • 1,088
  • 5
  • 11