C# XmlSerializer在实现IList时如何/为什么对类进行不同的处理?

C# XmlSerializer在实现IList时如何/为什么对类进行不同的处理?,c#,xmlserializer,C#,Xmlserializer,XmlSerializer正在我的类上调用IList.Add,我不明白为什么 我有一个自定义类,它是层次结构中几个类中的一个,包含使用XmlSerializer与XML进行转换的数据。在我的代码的前一个版本中,这些类没有实现任何接口,XML序列化和反序列化似乎都按预期工作 我现在正在编写使用此类中包含的数据的其他代码,我认为如果可以通过IList接口访问数据会很有帮助,因此我修改了类以实现该接口。本例中的T是我的另一个自定义类。这并不涉及向类中添加任何新字段;我根据已经存储的数据实现了所有这些功

XmlSerializer正在我的类上调用IList.Add,我不明白为什么

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

我现在正在编写使用此类中包含的数据的其他代码,我认为如果可以通过IList接口访问数据会很有帮助,因此我修改了类以实现该接口。本例中的T是我的另一个自定义类。这并不涉及向类中添加任何新字段;我根据已经存储的数据实现了所有这些功能

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

即使我的类的XML节点没有任何XML属性或子节点,也会发生这种情况;XmlSerializer显然仍在创建一个新的myOtherClass,它在XML文档中的任何地方都没有命名,并试图将其添加到myClass中

我在搜索这方面的信息时遇到了困难,因为大多数涉及XmlSerializer和IList的问题似乎都涉及试图序列化/反序列化IList类型变量的人。那不是我的情况;代码中没有IList类型的变量。如果我没有实现IList接口,我的类可以很好地序列化和反序列化

有人能解释为什么XmlSerializer在我的类上调用IList.Add,和/或如何停止它吗

建议最好与最终在Unity3d.NET 2.0中运行的代码兼容。

如果不可靠地再现问题,就不可能提供任何具体的答案

除此之外,以下是一些可以帮助您的非特定注释:

.NET序列化处理集合类型的方式不同于其他类型。默认情况下,实现任何IEnumerable接口(如IList)的类型被视为集合。这些类型通过枚举集合和存储单个元素来序列化。在反序列化时,.NET假定它可以使用Add方法填充反序列化对象。不幸的是,任何类型都会从Add方法抛出异常,或者更糟糕的是,根本没有实现异常。 在某些情况下,可以使用[DataContract]属性标记您的类型。这将覆盖默认行为,允许将您的类型视为非集合类型。 在其他情况下,您确实不应该首先实现IList,而是应该以不同的方式公开元素的枚举,例如作为返回枚举的属性。如果没有一个好的代码示例,那么任何代码示例都不可能说明这在您的场景中是否正确,但我认为至少有50/50的可能性。 如果不能可靠地再现问题,就不可能提供任何具体的答案

除此之外,以下是一些可以帮助您的非特定注释:

.NET序列化处理集合类型的方式不同于其他类型。默认情况下,实现任何IEnumerable接口(如IList)的类型被视为集合。这些类型通过枚举集合和存储单个元素来序列化。在反序列化时,.NET假定它可以使用Add方法填充反序列化对象。不幸的是,任何类型都会从Add方法抛出异常,或者更糟糕的是,根本没有实现异常。 在某些情况下,可以使用[DataContract]属性标记您的类型。这将覆盖默认行为,允许将您的类型视为非集合类型。 在其他情况下,您确实不应该首先实现IList,而是应该以不同的方式公开元素的枚举,例如作为返回枚举的属性。如果没有一个好的代码示例,那么任何代码示例都不可能说明这在您的场景中是否正确,但我认为至少有50/50的可能性。 要求所有集合都具有Add方法,如中所述:

XmlSerializer对实现IEnumerable或ICollection的类给予特殊处理。实现IEnumerable的类必须实现接受单个参数的公共Add方法。Add方法的参数的类型必须与从GetEnumerator返回的值的当前属性返回的类型相同,或者是该类型的基之一。一个实现ICollection的类,如CollectionBase和I Enumerable必须在C中具有接受整数的公共项索引属性索引器,并且必须具有integer类型的公共计数属性。Add方法的参数必须与从Item属性返回的参数类型相同,或者是该类型的基之一。对于实现ICollection的类,要序列化的值是从索引项属性中检索的,而不是通过调用GetEnumerator

此外,如果集合有自己的可设置属性,则不会序列化这些属性。这也在以下文件中详细说明:

可以使用XmLSerializer类序列化以下项:

实现ICollection或IEnumerable的类:只序列化集合,不序列化公共属性。 <> P>看看这在实践中是如何发挥的,请考虑下面的类:

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,我会得到:

要求所有集合都具有Add方法,如中所述:

XmlSerializer对实现IEnumerable或ICollection的类给予特殊处理。实现IEnumerable的类必须实现接受单个参数的公共Add方法。Add方法的参数的类型必须与从GetEnumerator返回的值的当前属性返回的类型相同,或者是该类型的基之一。除了IEnumerable之外,实现ICollection(如CollectionBase)的类必须在C中具有接受整数的公共项索引属性索引器,并且必须具有integer类型的公共计数属性。Add方法的参数必须与从Item属性返回的参数类型相同,或者是该类型的基之一。对于实现ICollection的类,要序列化的值是从索引项属性中检索的,而不是通过调用GetEnumerator

此外,如果集合有自己的可设置属性,则不会序列化这些属性。这也在以下文件中详细说明:

可以使用XmLSerializer类序列化以下项:

实现ICollection或IEnumerable的类:只序列化集合,不序列化公共属性。 <> P>看看这在实践中是如何发挥的,请考虑下面的类:

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,我会得到:


您能否不只是实现IReadOnlyList而不是IList?实现接口并在某些方法中抛出异常有点代码味道。我不熟悉IReadOnlyList,所以感谢您指出这一点。然而,这段代码在理想情况下应该在Unity3d中工作,我相信这将我限制在.NET2.0中。您能不能不实现IList而实现IReadOnlyList?实现接口并在某些方法中抛出异常有点代码味道。我不熟悉IReadOnlyList,所以感谢您指出这一点。但是,理想情况下,这段代码应该在Unity3d内部工作,我认为这将我限制在.NET 2.0中。您是说.NET序列化假定实现IEnumerable的任何对象上都存在Add方法,即使Add不是IEnumerable接口的一部分?假定是错误的词。在序列化上下文中,Requires更为正确。显然,IEnumerable接口本身不需要Add实现。但是序列化假定在某个时刻将进行反序列化;反序列化集合需要某种机制来填充该集合。Microsoft选择使需求简单且独立于实际实现的接口:类型必须具有Add方法。有关更多详细信息,请参阅。我认为您混淆了XmlSerializer和DataContractSerializer。添加[DataContract]对XmlSerializer没有影响。@dbc:您是正确的,因此在某些情况下,例如在使用DataContractSerializer时以及在隐式使用它的场景中。我的回答旨在大致解决集合序列化的一般问题。XmlSerializer与DataContractSerializer有许多相同的行为,因此我已经包含了与这两者相关的要点。您是说,.NET序列化假定任何实现IEnumerable的对象上都存在Add方法,即使Add不是IEnumerable接口的一部分?假定是错误的词。在序列化上下文中,Requires更为正确。显然,IEnumerable接口本身不需要Add实现。但是序列化假定在某个时刻将进行反序列化;反序列化集合需要某种机制来填充该集合。Microsoft选择使需求简单且独立于实际实现的接口:类型必须具有Add方法。有关更多详细信息,请参阅。我认为您混淆了XmlSerializer和DataContractSerializer。添加[DataContract]对XmlSerializer没有影响。@dbc:您是正确的,因此在某些情况下,例如在使用DataContractSerializer时以及在隐式使用它的场景中。我的回答旨在大致解决集合序列化的一般问题。XmlSeri alizer与DataContractSerializer有许多相同的行为,因此我已经包含了与这两者相关的要点。
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
    }
}
<ArrayOfDouble>
    <double>1</double>
    <double>2</double>
</ArrayOfDouble>
    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;
        }
    }