7

I'm trying to deserialize some settings from an xml file. The problematic property/underlying field is one called AlertColors. I initialize the underlying field to white, yellow, and red to make sure that a new instance of this class has a valid color setting. But when I deserialize, _colorArgb ends up with six values, first three are the initialization values and the last three are the ones that are read from the xml file. But the property AlertColors do not append to the field, but changes its elements. Why do I end up with a field with six colors?

Here's the code:

    private List<int> _colorArgb = new List<int>(new int[] { Color.White.ToArgb(), Color.Yellow.ToArgb(), Color.Red.ToArgb() });   

    public List<int> AlertColors
    {
        get
        {
            return _colorArgb;
        }

        set
        {
            for (int i = 0; i < Math.Min(_colorArgb.Count, value.Count); i++)
            {
                if (_colorArgb[i] != value[i])
                {
                    HasChanged = true;
                }
            }

            _colorArgb = value;
        }
    }

    public bool Deserialize(string filePath)
    {
        if (!File.Exists(filePath))
        {
            Logger.Log("Error while loading the settings. File does not exist.");

            return false;
        }

        FileStream fileStream = null;

        try
        {
            fileStream = new FileStream(filePath, FileMode.Open);
            System.Xml.Serialization.XmlSerializerFactory xmlSerializerFactory =
                new XmlSerializerFactory();
            System.Xml.Serialization.XmlSerializer xmlSerializer =
                xmlSerializerFactory.CreateSerializer(typeof(Settings));

            Settings deserializedSettings = (Settings)xmlSerializer.Deserialize(fileStream);

            GetSettings(deserializedSettings);

            Logger.Log("Settings have been loaded successfully from the file " + filePath);
        }
        catch (IOException iOException)
        {
            Logger.Log("Error while loading the settings. " + iOException.Message);

            return false;
        }
        catch (ArgumentException argumentException)
        {
            Logger.Log("Error while loading the settings. " + argumentException.Message);

            return false;
        }
        catch (InvalidOperationException invalidOperationException)
        {
            Logger.Log("Error while loading the settings. Settings file is not supported." +
                invalidOperationException.Message);

            return false;
        }
        finally
        {
            if (fileStream != null)
                fileStream.Close();

            FilePath = filePath;
        }

        return true;
    }

    protected void GetSettings(Settings settings)
    {
        AlertColors = settings.AlertColors;
    }

And the relevant part of the xml file that I'm deserializing:

  <AlertColors>
    <int>-1</int>
    <int>-15</int>
    <int>-65536</int>
  </AlertColors>
hattenn
  • 4,371
  • 9
  • 41
  • 80
  • Just as an aside: you do realize that the iteration for change detection misses for instance setting { White, White} when the current value is { White, White, Blue}? – Anton Oct 24 '12 at 10:00
  • Yeah I was thinking that if there are two values, it is for the first two. User shouldn't be manually changing them anyways. – hattenn Oct 24 '12 at 10:30
  • Related: http://stackoverflow.com/q/4217139/161052 – JYelton Jul 08 '14 at 22:22

3 Answers3

4

Basically, that's just how XmlSerializer works. Unless the list is null, it never expects to try and set a value. In particular, most of the time, sub-item lists don't have a setter - they are things like:

private readonly List<Child> children = new List<Child>();
public List<Child> Children { get { return children; } }

(because most people don't want external callers to reassign the list; they just want them to change the contents).

Because of this, XmlSerializer operates basically like (over-simplifying):

var list = yourObj.SomeList;
foreach({suitable child found in the data})
    list.Add({new item});

One fix is to use an array rather than a list; it always expects to assign an array back to the object, so for an array it is implemented more like (over-simplifying):

var list = new List<SomeType>();
foreach({suitable child found in the data})
    list.Add({new item});
yourObj.SomeList = list.ToArray();

However, for a fixed number of values, a simpler implementation might be just:

public Foo Value1 {get;set;}
public Foo Value2 {get;set;}
public Foo Value3 {get;set;}

(if you see what I mean)

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • But it does not serialize/deserialize the private `_colorArgb` field. How does it assume that I'm using that field for the property `AlertColors` and append to it? – hattenn Oct 24 '12 at 10:14
  • why not check if there is a setter... – Lamp Nov 17 '22 at 06:04
2

To get your desired result without changing your data types, you could use a DataContractSerializer (using System.Runtime.Serialization;) instead of the normal XmlSerializer. It doesn't call default constructors therefore you will end up with 3 colours instead of 6.

var ser = new DataContractSerializer(typeof(Settings));
var reader = new FileStream(@"c:\SettingsFile.xml", FileMode.Open);
var deserializedSettings = (Settings)ser.ReadObject(reader);
user1919249
  • 327
  • 1
  • 3
  • 10
2

Coming a bit late to the party, but I just ran into this problem as well.

The accepted answer mentions that Arrays are assigned to every time a deserialization occurs. This was very helpful. But I needed a solution that didn't require me to change the type of the properties and rewrite a million lines of code. So I came up with this:

Using XML Serializer attributes you can 'redirect' the serializer to an Array that wraps around the original property.

[XmlIgnore]
public List<int> AlertColors { get; set; } = new List<int>() { Color.White.ToArgb(), Color.Yellow.ToArgb(), Color.Red.ToArgb() });

[XmlArray(ElementName = "AlertColors")]
public long[] Dummy
{
      get
      {
          return AlertColors.ToArray();
      }
      set
      {
          if(value != null && value.Length > 0) AlertColors = new List<int>(value);
      }
}

The Dummy property has to be public in order for the serializer to access it. For me however this was a small price to pay, leaving the original property unchanged so I didn't have to modify any additional code.

Karim Ayachi
  • 547
  • 5
  • 11