12

I'm noticing an odd behavior with the XmlSerializer and generic lists (specifically List<int>). I was wondering if anyone has seen this before or knows what's going on. It appears as though the serialization works fine but the deserialization wants to add extra items to the list. The code below demonstrates the problem.

Serializable class:

public class ListTest
{
    public int[] Array { get; set; }
    public List<int> List { get; set; }

    public ListTest()
    {
        Array = new[] {1, 2, 3, 4};
        List = new List<int>(Array);
    }
}

Test code:

ListTest listTest = new ListTest();
Debug.WriteLine("Initial Array: {0}", (object)String.Join(", ", listTest.Array));
Debug.WriteLine("Initial List: {0}", (object)String.Join(", ", listTest.List));

XmlSerializer serializer = new XmlSerializer(typeof(ListTest));
StringBuilder xml = new StringBuilder();
using(TextWriter writer = new StringWriter(xml))
{
    serializer.Serialize(writer, listTest);
}

Debug.WriteLine("XML: {0}", (object)xml.ToString());

using(TextReader reader = new StringReader(xml.ToString()))
{
    listTest = (ListTest) serializer.Deserialize(reader);
}

Debug.WriteLine("Deserialized Array: {0}", (object)String.Join(", ", listTest.Array));
Debug.WriteLine("Deserialized List: {0}", (object)String.Join(", ", listTest.List));

Debug output:

Initial Array: 1, 2, 3, 4
Initial List: 1, 2, 3, 4

XML:

<?xml version="1.0" encoding="utf-16"?>
<ListTest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Array>
    <int>1</int>
    <int>2</int>
    <int>3</int>
    <int>4</int>
  </Array>
  <List>
    <int>1</int>
    <int>2</int>
    <int>3</int>
    <int>4</int>
  </List>
</ListTest>
Deserialized Array: 1, 2, 3, 4
Deserialized List: 1, 2, 3, 4, 1, 2, 3, 4

Notice that both the array and list appear to have serialized to XML correctly but on deserialization the array comes out correct but the list comes back with a duplicate set of items. Any ideas?

Stephan Bauer
  • 9,120
  • 5
  • 36
  • 58
daveaglick
  • 3,600
  • 31
  • 45
  • 1
    Thanks for the answers - as I mentioned in a comment below, I didn't realize the deserializer was able to mutate the state of members during deserialization (in most cases I think it just sets new values, right?). Is List the only class it can do this with? Is there some reference indicating which classes the deserializer can mutate? – daveaglick Mar 06 '12 at 18:33

2 Answers2

8

It happens because you are initializing the List in the constructor. When you go to deserialize, a new ListTest is created and then it populate the object from state.

Think of the workflow like this

  1. Create a new ListTest
  2. Execute the Constructor (add 1,2,3,4)
  3. Deserialize the xml state, and add 1,2,3,4 to the List

A simple solution would be to init the object outside the scope of the constructor.

public class ListTest
{
    public int[] Array { get; set; }
    public List<int> List { get; set; }

    public ListTest()
    {

    }

    public void Init() 
    {
        Array = new[] { 1, 2, 3, 4 };
        List = new List<int>(Array);
    }
}

ListTest listTest = new ListTest();
listTest.Init(); //manually call this to do the initial seed
4

The issue is that you're defining the original 1,2,3,4 in the List in the default constructor. Your deserializer is adding to the list, not defining it.

Babak Naffas
  • 12,395
  • 3
  • 34
  • 49
  • 4
    Thanks, moving the initialization out of the constructor did indeed solve the problem. I misunderstood how the deserializer works. I didn't realize it mutates existing objects, I thought it always used the set accessor to write entirely new instances of children to the newly constructed object (in which case the list created in the constructor would have just been forgotten). You learn something everyday. – daveaglick Mar 06 '12 at 18:31