如何/为什么XmlSerializer的治疗类不同,当它实现IList< T&GT ;? [英] How/why does XmlSerializer treat a class differently when it implements IList<T>?

查看:134
本文介绍了如何/为什么XmlSerializer的治疗类不同,当它实现IList< T&GT ;?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

的XmlSerializer 呼吁的IList< T>。新增()上我的课,我不明白为什么



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



我现在工作的一些其他的代码,使用了此类数据,我认为这将是有益的,如果我可以通过的IList<访问数据; T> 接口,所以我修改我的课堂要实现该接口。 (其中T在这种情况下,是我的自定义类中的另一个)。这并不涉及增加任何新的字段添加到类;我实现所有的所需的方法和属性在已经被存储在数据方面。



我希望这不会影响到系列化以任何方式。但是,反序列化XML数据转换成我的课的时候,事情现在要求新的添加()方法,我实现了作为一部分的IList< T> 界面(这是一个问题,因为这个特殊的列表 IsReadOnly 添加()抛出引发NotSupportedException )。



这甚至发生在我的课XML节点仅仅是< ; MyClass的/> 没有XML属性或任何子女;在的XmlSerializer 显然还是创建一个新的 myOtherClass (这不是XML文档中的任何地方命名),并试图添加()到 MyClass的



我遇到问题寻找这个信息,因为大多数问题含m 的XmlSerializer 的IList< T> 似乎涉及到人的努力序列化/反序列化类型的变量的IList< T> 。这不是我的情况;我有的IList<没有类型的变量; T> 中的代码的任何地方。我的类序列化,如果我不实现的IList<反序列化就好了; T> 接口



谁能解释我为什么的XmlSerializer 呼吁的IList< T>。新增()上我的课,和/或如何使它停下来?



建议最好应使用此代码最终内Unity3d(.NET 2.0)运行兼容。


解决方案

的XmlSerializer 需要的所有集合有一个添加()方法,如在的文档




XmlSerializer的给予特殊的待遇,实现IEnumerable或ICollection的类。实现IEnumerable必须实现一个类中的公共 添加 方法接受一个参数。在 添加 方法的参数必须是同一类型的如从返回的 电流 从返回的值属性的 的GetEnumerator ,或者该类型的基地之一。 ,除了IEnumerable的实现的ICollection(如CollectionBase的)A类必须有一个公共的 项目 索引属性(在C#索引器),它接受一个整数,并且它必须有一个公共的 计数整数类型属性。该参数的 添加 方式必须是从 项目返回同一类型 属性,或者该类型的基地之一。对于实现ICollection的类,要序列值从索引的 项目 属性检索,而不是通过调用 的GetEnumerator




此外,如果集合有自己的可设置的属性,这些不会被序列化的。

$ B:这也是在文档拼写
$ b

以下项目可使用XmlSerializer类序列化:




  • 类实现的ICollection或IEnumerable的:只有集合是序列化,而不是公共属性




要看到这是如何。在实践中发挥出来,考虑下面的类:

 命名空间V1 
{
// HTTPS:/ /stackoverflow.com/questions/31552724/how-why-does-xmlserializer-treat-a-class-differently-when-it-implements-ilistt
公共类Vector2
{
公共双X {搞定;组; }

公共双Y {搞定;组; }

公共Vector2(){}

公共Vector2(双X,双Y)
:这()
{
本.X = X;
this.Y = Y;
}

公共双本[INT坐标]
{
得到
{
开关(坐标)
{
的情况下0:
返回X;
案例1:
返回Ÿ;
默认:
抛出新ArgumentOutOfRangeException();
}
}

{
开关(坐标)
{
的情况下0:
X =值;
中断;
案例1:
Y =价值;
中断;
默认:
抛出新ArgumentOutOfRangeException();
}
}
}
}
}

如果我序列化此为XML,我得到:




 < Vector2> 
将X - →1&下; / X - GT;
< Y> 2'; / Y>
< / Vector2>




现在说我要的这个新版本实现的IList<双> 。我想补充的接口,并实现它,抛出异常为调整列表中的所有方法:

 命名空间V2 
{
// https://stackoverflow.com/questions/31552724/how-why-does-xmlserializer-treat-a-class-differently-when-it-implements-ilistt
公共类Vector2:V1。 Vector2,IList的<双>
{
公共Vector2():碱(){}

公共Vector2(双的x,双y)的:碱(X,Y){}

#地区的IList<双>会员

公众诠释的IndexOf(双项)
{
为(VAR I = 0; I<计数;我++)
如果(本[I] = =项目)
回报我;
返回-1;
}

公共无效插入(INT指数,双项)
{
抛出新NotImplementedException();
}

公共无效RemoveAt移除(INT指数)
{
抛出新NotImplementedException();
}

#endregion

#地区的ICollection<双>会员

公共无效添加(双项)
{
抛出新NotImplementedException();
}

公共无效清除()
{
抛出新NotImplementedException();
}

公共BOOL包含(双项)
{
返回的IndexOf(项目)> = 0;
}

公共无效CopyTo从(双[]数组,INT arrayIndex)
{
的foreach(在此VAR​​项目)
数组[arrayIndex ++] =项目;
}

公众诠释伯爵
{
得到{2; }
}

公共BOOL IsReadOnly
{
获得{返回true; }
}

公共BOOL删除(双项)
{
抛出新NotImplementedException();
}

#endregion

#地区的IEnumerable<双>会员

公众的IEnumerator<双>的GetEnumerator()
{
产量返回X;
收益率的回报ÿ;
}

#endregion

#地区的IEnumerable会员

的IEnumerator IEnumerable.GetEnumerator()
{
返回的GetEnumerator();
}

#endregion
}
}

现在,如果我序列化XML,我得到:




 < ArrayOfDouble> 
<双> 1 LT; /双>
<双> 2'; /双>
< / ArrayOfDouble>




正如你可以看到,现在序列化作为双打的集合,的可设置属性 X 省略的。于是,反序列化时,添加()方法将被调用,而不是为 X中的设置方法,并抛出一个异常。



如果我尝试实施 IReadOnlyList<双> 而不是的IList<双> 的XmlSerializer 构造函数现在抛出,因为缺少添加()方法的一个例外。



小提琴。



有没有办法让力的XmlSerializer 来对待集合作为一个简单的对象,而不是的实施的IXmlSerializable 和的做手工的,这是相当沉重的负担。 (这里的的用的 的DataContractSerializer ,即适用 [DataContract] 而不是 [CollectionDataContract ] - 。但的DataContractSerializer 并没有出台,直到NET 3.5的,所以这是输出)



相反贯彻的IList< T> ,你可能想简单介绍一个扩展方法通过数值在你的类进行迭代,像这样:

 公共静态类Vector2Extensions 
{
公共静态的IEnumerable<双>值(此Vector2 VEC)
{
如果(VEC == NULL)
抛出新的ArgumentNullException();
产量返回vec.X;
产量返回vec.Y;
}
}


XmlSerializer is calling IList<T>.Add() on my class and I don't understand why.

I have a custom class (one of several classes in a hierarchy) containing data that I am converting to and from XML using XmlSerializer. In a previous version of my code, these classes did not implement any interfaces, and XML serialization and deserialization both seemed to work as expected.

I'm now working on some other code that uses the data contained in this class, and I thought it would be helpful if I could access the data through the IList<T> interface, so I modified my class to implement that interface. (The "T" in this case is another one of my custom classes.) This didn't involve adding any new fields to the class; I implemented all the required methods and properties in terms of data that was already being stored.

I was hoping that this would not affect the serialization in any way. However, when deserializing XML data into my class, something is now calling the new Add() method I implemented as part of the IList<T> interface (which is a problem because this particular list IsReadOnly and so Add() throws a NotSupportedException).

This happens even when the XML node for my class is simply <myClass/> with no XML attributes or children whatsoever; the XmlSerializer is apparently still creating a new myOtherClass (which is not named anywhere in the XML document) and trying to Add() it to the myClass.

I'm having trouble searching for information in this, because most questions involving XmlSerializer and IList<T> seem to involve people trying to serialize/deserialize a variable of type IList<T>. That is NOT my situation; I have no variables of type IList<T> anywhere in the code. My class serializes and deserializes just fine if I do NOT implement the IList<T> interface.

Can anyone explain to me why XmlSerializer is calling IList<T>.Add() on my class, and/or how to make it stop?

Suggestions should ideally be compatible with this code eventually running inside Unity3d (.NET 2.0).

解决方案

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. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item indexed property (indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter to the Add method must be the same type as is returned from the Item property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexed Item property, not by calling GetEnumerator.

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;
        }
    }

这篇关于如何/为什么XmlSerializer的治疗类不同,当它实现IList&LT; T&GT ;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆