XmlSerializer
is calling IList
on my class and I don\'t understand why.
I have a custom class (one of several classes in a h
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. TheAdd
method's parameter must be of the same type as is returned from theCurrent
property on the value returned fromGetEnumerator
, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a publicItem
indexed property (indexer in C#) that takes an integer, and it must have a publicCount
property of type integer. The parameter to theAdd
method must be the same type as is returned from theItem
property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexedItem
property, not by callingGetEnumerator
.
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;
}
}
Without a good, minimal, complete code example that reliably reproduces the problem, it will be impossible to provide any specific answer.
In lieu of that, here are some non-specific notes that may help you:
IEnumerable
interface (e.g. IList<T>
) is considered a collection. Such types are serialized by enumerating the collection and storing the individual elements. On deserialization, .NET assumes it can use an Add()
method to populate the deserialized object. Woe unto any type that throws an exception from the Add()
method or, worse, doesn't implement one at all.[DataContract]
attribute. This overrides the default behavior, allowing your type to be treated as a non-collection type.IList<T>
in the first place, but instead should have exposed the enumeration of elements differently, e.g. as a property that returns the enumeration. Lacking a good code example (well, any code example) it's not possible to say whether this is true in your scenario or not, but I'd say there's at least a 50/50 chance it is.