Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xml deserialization appends to list

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>
like image 365
hattenn Avatar asked Oct 24 '12 09:10

hattenn


2 Answers

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)

like image 62
Marc Gravell Avatar answered Nov 14 '22 03:11

Marc Gravell


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);
like image 22
user1919249 Avatar answered Nov 14 '22 04:11

user1919249