XmlSerializer在实现IList 时如何/以不同方式处理类?
XmlSerializer
在我的类上调用IList.Add()
,我不明白为什么。
我有一个自定义类(层次结构中的几个类之一),包含我使用XmlSerializer
转换为XML的数据。 在我的代码的先前版本中,这些类没有实现任何接口,并且XML序列化和反序列化似乎都按预期工作。
我现在正在研究使用此类中包含的数据的其他代码,我认为如果我可以通过IList
接口访问数据会很有用,所以我修改了我的类以实现该接口。 (在这种情况下,“T”是我的另一个自定义类。)这不涉及向类中添加任何新字段; 我根据已经存储的数据实现了所有必需的方法和属性 。
我希望这不会以任何方式影响序列化。 但是,当将XML数据反序列化到我的类时,现在正在调用我作为IList
接口的一部分实现的新的Add()
方法(这是一个问题,因为这个特定的列表是IsReadOnly
,因此Add()
抛出NotSupportedException
) 。
即使我的类的XML节点只是而没有任何XML属性或子节点,也会发生这种情况;
XmlSerializer
显然仍在创建一个新的myOtherClass
(它没有在XML文档中的任何地方命名)并尝试Add()
它到myClass
。
我在搜索信息方面遇到了麻烦,因为涉及XmlSerializer
和IList
大多数问题似乎都涉及到人们试图序列化/反序列化IList
类型的变量。 那不是我的情况; 我在代码中的任何地方都没有IList
类型的变量。 如果我不实现IList
接口,我的类序列化和反序列化就好了。
任何人都可以向我解释为什么XmlSerializer
在我的类上调用IList.Add()
,和/或如何让它停止?
理想情况下,建议应该与最终在Unity3d(.NET 2.0)中运行的代码兼容。
XmlSerializer
要求所有集合都有一个Add()
方法,如文档中所述:
XmlSerializer为实现IEnumerable或ICollection的类提供特殊处理。 实现IEnumerable的类必须实现一个带有单个参数的公共
Add
方法。Add
方法的参数必须与从GetEnumerator
返回的值的Current
属性返回的类型相同,或者是该类型的base之一。 除了IEnumerable之外,实现ICollection(例如CollectionBase)的类必须具有一个公共Item
索引属性(C#中的索引器),该属性采用整数,并且它必须具有integer类型的公共Count
属性。Add
方法的参数必须与Item
属性返回的类型相同,或者是该类型的基础之一。 对于实现ICollection的类,要从序列化的Item
属性中检索要序列化的值,而不是通过调用GetEnumerator
。
此外,如果集合具有自己的可设置属性, 则不会序列化这些属性。 这也在文档中详细说明:
可以使用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,我得到:
1 2
现在说我想要一个实现IList
的新版本。 我添加了接口并实现它,为调整列表大小的所有方法抛出exception:
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 { public Vector2() : base() { } public Vector2(double x, double y) : base(x, y) { } #region IList 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 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 Members public IEnumerator GetEnumerator() { yield return X; yield return Y; } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } }
现在,如果我序列化XML,我得到:
1 2
如您所见,它现在序列化为双精度集合, 省略了可设置的属性X
和Y
然后,当反序列化时,将调用Add()
方法而不是X
和Y
的set方法,并抛出exception。
如果我尝试实现IReadOnlyList
而不是IList
, XmlSerializer
构造函数现在抛出exception,因为缺少Add()
方法。
示例小提琴 。
强制XmlSerializer
无法将集合视为一个简单的对象,除了实现IXmlSerializable
并手动执行 ,这是非常繁重的。 ( DataContractSerializer
有一个解决方法,即应用[DataContract]
而不是[CollectionDataContract]
– 但是直到.Net 3.5才引入DataContractSerializer
,所以那就是了。)
您可能希望简单地引入一个扩展方法来迭代类中的值,而不是实现IList
,如下所示:
public static class Vector2Extensions { public static IEnumerable Values(this Vector2 vec) { if (vec == null) throw new ArgumentNullException(); yield return vec.X; yield return vec.Y; } }
如果没有可靠地再现问题的良好, 最小化 , 完整的代码示例 ,则无法提供任何具体答案。
除此之外,以下是一些可能对您有帮助的非特定说明:
- .NET序列化处理集合类型与其他类型不同。 默认情况下,实现任何
IEnumerable
接口的类型(例如IList
)被视为集合。 通过枚举集合和存储各个元素来序列化这些类型。 在反序列化时,.NET假定它可以使用Add()
方法来填充反序列化的对象。 从Add()
方法抛出exception的任何类型都有祸了,或者更糟糕的是,根本没有实现。 - 在某些情况下,使用
[DataContract]
属性标记类型可能是合适的。 这将覆盖默认行为,允许将您的类型视为非集合类型。 - 在其他情况下,您实际上不应该首先实现
IList
,而应该以不同方式公开元素的枚举,例如作为返回枚举的属性。 缺乏一个好的代码示例(好吧, 任何代码示例),不可能在你的场景中说明这是否属实,但我认为它至少有50/50的可能性。