XmlSerializer
requires all collections to have an Add()
method, as is spelled out in the documentation:
The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add
method that takes a single parameter. The Add
method's parameter must be of the same type as is returned from the Current
property on the value returned from GetEnumerator
, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item
indexed property (indexer in C#) that takes an integer, and it must have a public Count
property of type integer. The parameter to the Add
method must be the same type as is returned from the Item
property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexed Item
property, not by calling GetEnumerator
.
Further, if a collection has its own settable properties, these will not be serialized. This is also spelled out in the docs:
The following items can be serialized using the XmLSerializer class:
- Classes that implement ICollection or IEnumerable: Only collections are serialized, not public properties.
To see how this plays out in practice, consider the following class:
namespace V1
{
// https://stackoverflow.com/questions/31552724/how-why-does-xmlserializer-treat-a-class-differently-when-it-implements-ilistt
public class Vector2
{
public double X { get; set; }
public double Y { get; set; }
public Vector2() { }
public Vector2(double x, double y)
: this()
{
this.X = x;
this.Y = y;
}
public double this[int coord]
{
get
{
switch (coord)
{
case 0:
return X;
case 1:
return Y;
default:
throw new ArgumentOutOfRangeException();
}
}
set
{
switch (coord)
{
case 0:
X = value;
break;
case 1:
Y = value;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}
If I serialize this to XML, I get:
<Vector2>
<X>1</X>
<Y>2</Y>
</Vector2>
Now say I want a new version of this that implements IList<double>
. I add the interface and implement it, throwing exceptions for all methods that resize the list:
namespace V2
{
// https://stackoverflow.com/questions/31552724/how-why-does-xmlserializer-treat-a-class-differently-when-it-implements-ilistt
public class Vector2 : V1.Vector2, IList<double>
{
public Vector2() : base() { }
public Vector2(double x, double y) : base(x, y) { }
#region IList<double> Members
public int IndexOf(double item)
{
for (var i = 0; i < Count; i++)
if (this[i] == item)
return i;
return -1;
}
public void Insert(int index, double item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
#endregion
#region ICollection<double> Members
public void Add(double item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(double item)
{
return IndexOf(item) >= 0;
}
public void CopyTo(double[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count
{
get { return 2; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(double item)
{
throw new NotImplementedException();
}
#endregion
#region IEnumerable<double> Members
public IEnumerator<double> GetEnumerator()
{
yield return X;
yield return Y;
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}
Now if I serialize the XML, I get:
<ArrayOfDouble>
<double>1</double>
<double>2</double>
</ArrayOfDouble>
As you can see, it now serializes as a collection of doubles, with the settable properties X
and Y
omitted. Then, when deserialized, the Add()
method will get called instead of the set methods for X
and Y
, and throw an exception.
If I try to implement IReadOnlyList<double>
instead of IList<double>
, the XmlSerializer
constructor now throws an exception because of the missing Add()
method.
Example fiddle.
There is no way for force XmlSerializer
to treat a collection as a straightforward object, other than to implement IXmlSerializable
and do it manually, which is quite burdensome. (There is a workaround with DataContractSerializer
, namely to apply [DataContract]
instead of [CollectionDataContract]
-- however DataContractSerializer
was not introduced until .Net 3.5., so that's out.)
Instead of implementing IList<T>
, you might want to simply introduce an extension method to iterate through the values in your class, like so:
public static class Vector2Extensions
{
public static IEnumerable<double> Values(this Vector2 vec)
{
if (vec == null)
throw new ArgumentNullException();
yield return vec.X;
yield return vec.Y;
}
}