XmlSerializer在实现IList <t>时如何/不同地处理类?

时间:2015-07-22 02:12:02

标签: c# xmlserializer

XmlSerializer正在课堂上调用IList<T>.Add(),我不明白为什么。

我有一个自定义类(层次结构中的几个类之一),包含我使用XmlSerializer转换为XML的数据。在我的代码的先前版本中,这些类没有实现任何接口,并且XML序列化和反序列化似乎都按预期工作。

我现在正在处理一些使用此类中包含的数据的其他代码,我认为如果我可以通过IList<T>接口访问数据会很有帮助,所以我修改了我的类来实现那个界面。 (在这种情况下,“T”是我的另一个自定义类。)这不涉及向类中添加任何新字段;我根据已经存储的数据实现了所有required methods and properties

我希望这不会以任何方式影响序列化。但是,当将XML数据反序列化到我的类时,现在正在调用我作为Add()接口的一部分实现的新IList<T>方法(这是一个问题因为这个特定的列表IsReadOnly所以Add()会抛出NotSupportedException)。

即使我的类的XML节点只是<myClass/>而没有任何XML属性或子节点,也会发生这种情况; XmlSerializer显然仍然在创建一个新的myOtherClass(在XML文档中的任何位置都没有命名)并尝试将Add()创建为myClass

我在搜索此信息时遇到问题,因为涉及XmlSerializerIList<T>的大多数问题似乎都涉及到尝试序列化/反序列化IList<T>类型变量的人。那不是我的情况;我在代码中的任何地方都没有IList<T>类型的变量。如果我没有实现IList<T>接口,我的类序列化和反序列化就好了。

有人可以向我解释为什么XmlSerializer在课堂上调用IList<T>.Add(),和/或如何让它停止?

理想情况下,建议应该与最终在Unity3d(.NET 2.0)中运行的代码兼容。

2 个答案:

答案 0 :(得分:4)

XmlSerializer 要求所有馆藏都有Add()方法,正如documentation中所述:

  

XmlSerializer为实现IEnumerable或ICollection的类提供特殊处理。实现IEnumerable的类必须实现一个带有单个参数的公共 Add 方法。 Add 方法的参数必须与 Current 属性从返回的值返回的类型相同> GetEnumerator ,或其中一种类型的基础。除了IEnumerable之外,实现ICollection(例如CollectionBase)的类必须具有公共 Item 索引属性(C#中的索引器),它采用整数,并且必须具有公共 Count 类型为integer的属性。 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>

如您所见,它现在序列化为一系列双打,,其中包含可设置的属性XY 。然后,在反序列化时,将调用Add()方法而不是XY的set方法,并抛出异常。

如果我尝试实现IReadOnlyList<double>而不是IList<double>,那么XmlSerializer构造函数会因为缺少Add()方法而抛出异常。

示例fiddle

强制XmlSerializer无法将集合视为一个简单的对象,而不是implement IXmlSerializabledo it manually,这是非常繁重的。 (DataContractSerializer的解决方法,即应用[DataContract]而不是[CollectionDataContract] - 但是{。}}直到.Net 3.5才会引入DataContractSerializer。 ,那就出来了。)

您可能希望简单地引入一个扩展方法来迭代类中的值,而不是实现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;
        }
    }

答案 1 :(得分:0)

如果没有a good, minimal, complete code example可靠地重现问题,就无法提供任何具体答案。

除此之外,以下是一些可能对您有所帮助的非特定说明:

  1. .NET序列化处理集合类型与其他类型不同。默认情况下,实现任何IEnumerable接口的类型(例如IList<T>)被视为集合。通过枚举集合和存储各个元素来序列化这些类型。在反序列化时,.NET假定它可以使用Add()方法来填充反序列化的对象。从Add()方法抛出异常的任何类型都有祸了,或者更糟糕的是,根本没有实现一个异常。
  2. 在某些情况下,使用[DataContract]属性标记您的类型可能是合适的。这将覆盖默认行为,允许将您的类型视为非集合类型。
  3. 在其他情况下,您实际上不应该首先实现IList<T>,而应该以不同方式公开元素的枚举,例如作为返回枚举的属性。缺乏一个好的代码示例(好吧,任何代码示例),不可能在你的场景中说明这是否属实,但是我会说那里有#s / s它至少有50/50的机会。