5

I am confused on how XmlSerializer works behind the scenes. I have a class that deserializes XML into an object. What I am seeing is for the following two elements that are NOT part of the Xml being deserialized.

[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
    private string comments;
    public string Comments
    {
        set { comments = value; }
        get { return comments; }
    }

    private System.Collections.Generic.List<string> tests = null;
    public System.Collections.Generic.List<string> Tests
    {
        get { return tests; }
        set { tests = value; }
    }
}

Let's take the following XML as an example:

<MyClass>
  <SomeNode>value</SomeNode>
</MyClass>

You notice that Tests and Comments are NOT part of the XML.

When this XML gets deserialized Comments is null(which is expected) and Tests is an empty list with a count of 0.

If someone could explain this to me it would be much appreciated. What I would prefer is that if <Tests> is missing from the XML then the list should remain null, but if a (possibly empty) node <Tests /> is present then the list should get allocated.

dbc
  • 104,963
  • 20
  • 228
  • 340
Maxqueue
  • 2,194
  • 2
  • 23
  • 55
  • @gdir are you saying when you serialize this you get a null value for a list instead of empty list? – Maxqueue Jul 27 '17 at 19:18
  • No what i am asking is that since tests is NOT part of the xml that when serialized it should be null instead of empty list. Does that make sense? – Maxqueue Jul 27 '17 at 19:21
  • 2
    OP's question is about deserialization not serialization. When the XML above is deserialize the `tests` collection is allocated even though `` never appears in the XML. (I can reproduce it, by the way.) – dbc Jul 27 '17 at 19:23
  • Edited to deserialize thanks – Maxqueue Jul 27 '17 at 19:28
  • I don't know any place this behavior is documented. When `` is completely missing you don't want the list to be allocated. What do you want when there is an empty `` node? – dbc Jul 27 '17 at 19:39
  • To me it makes sense for the case of to be empty list. But if tests is not present it should be null. – Maxqueue Jul 27 '17 at 19:44
  • Turns out it behaves as expected when using an array instead of a list. – Maxqueue Jul 27 '17 at 19:56

4 Answers4

5

What you are observing is that members referring to modifiable collections such as List<T> are automatically pre-allocated by XmlSerializer at the beginning of deserialization. I am not aware of any place where this behavior is documented. It may be related to the behavior described in this answer to XML Deserialization of collection property with code defaults, which explains that, since XmlSerializer supports adding to get-only and pre-allocated collections, if a pre-allocated collection contains default items then the deserialized items will be appended to it - possibly duplicating the contents. Microsoft may simply have chosen to pre-allocate all modifiable collections at the beginning of deserialization as the simplest way of implementing this.

The workaround from that answer, namely to use a surrogate array property, works here as well. Since an array cannot be appended to, XmlSerializer must accumulate all the values and set them back when deserialization is finished. But if the relevant tag is never encountered, XmlSerializer apparently does not begin accumulating values and so does not call the array setter. This seems to prevent the default pre-allocation of collections that you don't want:

[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
    private string comments;
    public string Comments
    {
        set { comments = value; }
        get { return comments; }
    }

    private System.Collections.Generic.List<string> tests = null;

    [XmlIgnore]
    public System.Collections.Generic.List<string> Tests
    {
        get { return tests; }
        set { tests = value; }
    }

    [XmlArray("Tests")]
    public string[] TestsArray
    {
        get
        {
            return (Tests == null ? null : Tests.ToArray());
        }
        set
        {
            if (value == null)
                return;
            (Tests = Tests ?? new List<string>(value.Length)).AddRange(value);
        }
    }
}

Sample .Net fiddle showing that Tests is allocated only when appropriate.

dbc
  • 104,963
  • 20
  • 228
  • 340
1

When you apply [System.Xml.Serialization.XmlElement(IsNullable = true)] to the property, the List will be null after deserialization.

Don Kedero
  • 61
  • 2
1

Another possibility is to use the "magic" "Specified" suffix:

public bool TestsSpecified {get;set;}

If you have a serialized field/property XXX and a boolean property XXXSpecified, then the bool property is set according to whether or not the main field/property was set.

0

We wound up here after a google search for the same issue. What we ended up doing was checking for Count == 0, after deserialization, and manually setting the property to null;

...
var varMyDeserializedClass = MyXmlSerializer.Deserialize(new StringReader(myInput));
if (varMyDeserializedClass.ListProperty.Count == 0)
{
  varMyDeserializedClass.ListProperty = null;
}
...

It's a cheap workaround, but provides the expected result and is useful to avoid refactoring or redesign.

Itaca
  • 39
  • 6