XmlSerializer 要求所有集合都具有Add() 方法,如documentation 中所述:
XmlSerializer 对实现 IEnumerable 或 ICollection 的类进行特殊处理。实现 IEnumerable 的类必须实现采用单个参数的公共 Add 方法。 Add 方法的参数必须与从 GetEnumerator 返回的值的 Current 属性返回的类型相同,或该类型的基础之一。除了 IEnumerable 之外,实现 ICollection 的类(例如 CollectionBase)必须有一个公共 Item 索引属性(C# 中的索引器),它必须有一个公共 @ 987654335@ 整数类型的属性。 Add 方法的参数必须与从 Item 属性返回的类型相同,或者是该类型的基数之一。对于实现 ICollection 的类,要序列化的值是从索引的 Item 属性中检索的,而不是通过调用 GetEnumerator。
此外,如果一个集合有自己的可设置属性,这些将不会被序列化。这在docs中也有说明:
可以使用 XmlSerializer 类对以下项目进行序列化:
- 实现 ICollection 或 IEnumerable 的类:仅对集合进行序列化,而不对公共属性进行序列化。
要了解这在实践中如何发挥作用,请考虑以下课程:
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();
}
}
}
}
}
如果我将它序列化为 XML,我会得到:
<Vector2>
<X>1</X>
<Y>2</Y>
</Vector2>
现在说我想要一个实现IList<double> 的新版本。我添加接口并实现它,为调整列表大小的所有方法抛出异常:
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
}
}
现在如果我序列化 XML,我会得到:
<ArrayOfDouble>
<double>1</double>
<double>2</double>
</ArrayOfDouble>
如您所见,它现在序列化为双精度集合,省略了可设置的属性 X 和 Y。然后,在反序列化时,将调用Add() 方法而不是X 和Y 的set 方法,并抛出异常。
如果我尝试实现IReadOnlyList<double> 而不是IList<double>,则XmlSerializer 构造函数现在会由于缺少Add() 方法而引发异常。
例如fiddle。
除了implement IXmlSerializable 和do it manually 之外,没有办法强制XmlSerializer 将集合视为一个简单的对象,这相当繁琐。 (有一个使用DataContractSerializer 的解决方法,即应用[DataContract] 而不是[CollectionDataContract]——但是DataContractSerializer 直到.Net 3.5 才被引入,所以就这样了。) p>
您可能不想实现IList<T>,而是简单地引入一个扩展方法来遍历类中的值,如下所示:
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;
}
}