Reputation: 4399
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>
Upvotes: 7
Views: 1661
Reputation: 717
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.
Upvotes: 2
Reputation: 377
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);
Upvotes: 2
Reputation: 1062925
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)
Upvotes: 4