XmlInclude:列表和数组

时间:2016-03-30 12:03:30

标签: c# arrays list xml-serialization xmlserializer

我有一个变量为object的对象,我想用XML序列化它。

为此,我添加了一些XmlInclude属性,以便管理可以使用的所有类型。

[Serializable]
[XmlInclude(typeof(short[]))]
[XmlInclude(typeof(ushort[]))]
[XmlInclude(typeof(int[]))]
[XmlInclude(typeof(uint[]))]
[XmlInclude(typeof(ulong[]))]
[XmlInclude(typeof(long[]))]
[XmlInclude(typeof(byte[]))]
[XmlInclude(typeof(decimal[]))]
[XmlInclude(typeof(float[]))]
[XmlInclude(typeof(double[]))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(List<short>))]
[XmlInclude(typeof(List<ushort>))]
[XmlInclude(typeof(List<int>))]
[XmlInclude(typeof(List<uint>))]
[XmlInclude(typeof(List<long>))]
[XmlInclude(typeof(List<ulong>))]
[XmlInclude(typeof(List<byte>))]
[XmlInclude(typeof(List<decimal>))]
[XmlInclude(typeof(List<float>))]
[XmlInclude(typeof(List<double>))]
[XmlInclude(typeof(List<string>))]
[XmlInclude(typeof(MyObject))]
[XmlInclude(typeof(TimeSpan))]
[XmlInclude(typeof(OtherObject))]
[XmlInclude(typeof(MySubObject1))]
[XmlInclude(typeof(MySubObject2))]
[XmlRoot(ElementName = "mc")]
public class MyClass: IComparable
{
    [XmlElement("fm")]
    public object FirstMember;

    [XmlElement("sm")]
    public object SecondMember;

    [XmlElement("tm")]
    public object ThirdMember;
}

我的问题是数组和列表声明不会共存。

奇怪的是,如果首先放置数组属性,则数组成员被正确序列化,但不是列表成员。反之亦然。

自定义类和派生类可以正常工作,但ListArray并不适用。我只能找到类的例子,但我使用原始类型。

有没有人有想法?

P.S。:我知道我的帖子与this one相似,但自2011年以来没有答案。

1 个答案:

答案 0 :(得分:1)

我可以重现这个问题。之所以会发生这种情况是因为XmlSerializer为数组和列表(在这种情况下为字符串)生成相同的XML:

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ArrayOfString">
        <string>list</string>
        <string>entry</string>
    </fm>
</mc>

由于序列化程序对"ArrayOfString"string[]使用相同的xsi:type多态名称List<string>,当它找到可能遇到两者的情况时,它会抛出一个例外,因为它无法区分它们。

为什么XmlSerializer同时使用相同的名称?我只能猜测它能够交换通过序列化不同集合类型(例如从List<TElement>SortedSet<TElement>)创建的XML,而无需修复XML文件格式。

但是,在您的情况下,您需要在XML中区分这些类型的集合,而不是交换它们。因此,您将需要创建某种包装类,以允许在文件中区分它们。

例如,请考虑以下类的简化:

[XmlInclude(typeof(string))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(object[]))]
[XmlInclude(typeof(List<string>))]
[XmlInclude(typeof(List<object>))]
[XmlInclude(typeof(SortedSet<string>))]
[XmlInclude(typeof(SortedSet<object>))]
[XmlInclude(typeof(HashSet<string>))]
[XmlInclude(typeof(HashSet<object>))]
[XmlInclude(typeof(LinkedList<string>))]
[XmlInclude(typeof(LinkedList<object>))]
[XmlRoot(ElementName = "mc")]
public class MyClass
{
    [XmlElement("fm")]
    public object FirstMember;
}

这里FirstMember可以包含字符串,字符串或对象的数组,或各种类型的字符串或对象集合。

要为各种类型的集合建立不同的xsi:type值,可以引入以下通用包装类型:

/// <summary>
/// Abstract base type for a generic collection wrapper where, to differentiate 
/// between arrays and lists and other types of collections of the same underlying
/// item type, it is necessary to introduce an intermediary type to establish 
/// distinct xsi:type values.
/// </summary>
public abstract class CollectionWrapper
{
    [XmlIgnore]
    public abstract IEnumerable RealCollection { get; }

    static bool TryCreateWrapperType<TElement>(Type actualType, out Type wrapperType)
    {
        if (actualType.IsArray 
            || actualType.IsPrimitive 
            || actualType == typeof(string) 
            || !typeof(IEnumerable).IsAssignableFrom(actualType)
            || actualType == typeof(TElement) // Not polymorphic
            || !actualType.IsGenericType)
        {
            wrapperType = null;
            return false;
        }
        var args = actualType.GetGenericArguments();
        if (args.Length != 1)
        {
            wrapperType = null;
            return false;
        }
        if (actualType.GetGenericTypeDefinition() == typeof(List<>))
        {
            wrapperType = typeof(ListWrapper<>).MakeGenericType(args);
        }
        else if (actualType.GetGenericTypeDefinition() == typeof(HashSet<>))
        {
            wrapperType = typeof(HashSetWrapper<>).MakeGenericType(args);
        }
        else if (actualType.GetGenericTypeDefinition() == typeof(SortedSet<>))
        {
            wrapperType = typeof(SortedSetWrapper<>).MakeGenericType(args);
        }
        else 
        {
            var collectionTypes = actualType.GetCollectionItemTypes().ToList();
            if (collectionTypes.SequenceEqual(args))
                wrapperType = typeof(CollectionWrapper<,>).MakeGenericType(new [] { actualType, args[0] });
            else
            {
                wrapperType = null;
                return false;
            }
        }

        if (!typeof(TElement).IsAssignableFrom(wrapperType))
        {
            wrapperType = null;
            return false;
        }
        return true;
    }

    public static TElement Wrap<TElement>(TElement item)
    {
        if (item == null)
            return item;
        var type = item.GetType();
        if (type == typeof(TElement))
            return item;
        Type wrapperType;
        if (!TryCreateWrapperType<TElement>(type, out wrapperType))
            return item;
        return (TElement)Activator.CreateInstance(wrapperType, item);
    }

    public static TElement Unwrap<TElement>(TElement item)
    {
        if (item is CollectionWrapper)
            return (TElement)((CollectionWrapper)(object)item).RealCollection;
        return item;
    }
}

/// <summary>
/// Generic wrapper type for a generic collection of items.
/// </summary>
/// <typeparam name="TCollection"></typeparam>
/// <typeparam name="TElement"></typeparam>
public class CollectionWrapper<TCollection, TElement> : CollectionWrapper where TCollection : ICollection<TElement>, new()
{
    public class CollectionWrapperEnumerable : IEnumerable<TElement>
    {
        readonly TCollection collection;

        public CollectionWrapperEnumerable(TCollection collection)
        {
            this.collection = collection;
        }

        public void Add(TElement item)
        {
            collection.Add(CollectionWrapper.Unwrap<TElement>(item));
        }

        #region IEnumerable<TElement> Members

        public IEnumerator<TElement> GetEnumerator()
        {
            foreach (var item in collection)
                yield return CollectionWrapper.Wrap(item);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    readonly TCollection collection;
    readonly CollectionWrapperEnumerable enumerable;

    public CollectionWrapper()
        : this(new TCollection())
    {
    }

    public CollectionWrapper(TCollection collection)
    {
        if (collection == null)
            throw new ArgumentNullException();
        this.collection = collection;
        this.enumerable = new CollectionWrapperEnumerable(collection);
    }

    [XmlElement("Item")]
    public CollectionWrapperEnumerable SerializableEnumerable { get { return enumerable; } }

    [XmlIgnore]
    public override IEnumerable RealCollection { get { return collection; } }
}

// These three subclasses of CollectionWrapper for commonly encounterd collections were introduced to improve readability

public class ListWrapper<TElement> : CollectionWrapper<List<TElement>, TElement>
{
    public ListWrapper() : base() { }

    public ListWrapper(List<TElement> list) : base(list) { }
}

public class HashSetWrapper<TElement> : CollectionWrapper<HashSet<TElement>, TElement>
{
    public HashSetWrapper() : base() { }

    public HashSetWrapper(HashSet<TElement> list) : base(list) { }
}

public class SortedSetWrapper<TElement> : CollectionWrapper<SortedSet<TElement>, TElement>
{
    public SortedSetWrapper() : base() { }

    public SortedSetWrapper(SortedSet<TElement> list) : base(list) { }
}

public static class TypeExtensions
{
    /// <summary>
    /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectionItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

然后在您的简化课程中使用如下:

[XmlInclude(typeof(string))]
[XmlInclude(typeof(string[]))]
[XmlInclude(typeof(object[]))]
[XmlInclude(typeof(ListWrapper<string>))]
[XmlInclude(typeof(ListWrapper<object>))]
[XmlInclude(typeof(SortedSetWrapper<string>))]
[XmlInclude(typeof(SortedSetWrapper<object>))]
[XmlInclude(typeof(HashSetWrapper<string>))]
[XmlInclude(typeof(HashSetWrapper<object>))]
[XmlInclude(typeof(CollectionWrapper<LinkedList<string>, string>))]
[XmlInclude(typeof(CollectionWrapper<LinkedList<object>, object>))]
[XmlRoot(ElementName = "mc")]
public class MyClass
{
    [XmlElement("fm")]
    [JsonIgnore]
    public object XmlFirstMember
    {
        get
        {
            return CollectionWrapper.Wrap(FirstMember);
        }
        set
        {
            FirstMember = CollectionWrapper.Unwrap(value);
        }
    }

    [XmlIgnore]
    public object FirstMember;
}

然后,对于一个简单的字符串列表:

var myClass = new MyClass { FirstMember = new List<string> { "list", "entry" } };

生成以下XML:

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ListWrapperOfString">
        <Item>list</Item>
        <Item>entry</Item>
    </fm>
</mc>

如您所见,xsi:type现在是截然不同的。

如果我创建以下更复杂的对象:

var myClass = new MyClass
{
    FirstMember = new List<object>
    {
        new List<object> { new List<object> { new List<object> { "hello" } }, "there" },
        new HashSet<string> { "hello", "hello", "there" },
        new SortedSet<string> { "hello", "hello", "there" },
        new LinkedList<object>( new object [] { new LinkedList<string>( new [] { "hello", "there" }) }),
    }
};

生成以下XML:

<mc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <fm xsi:type="ListWrapperOfObject">
        <Item xsi:type="ListWrapperOfObject">
            <Item xsi:type="ListWrapperOfObject">
                <Item xsi:type="ListWrapperOfObject">
                    <Item xsi:type="xsd:string">hello</Item>
                </Item>
            </Item>
            <Item xsi:type="xsd:string">there</Item>
        </Item>
        <Item xsi:type="HashSetWrapperOfString">
            <Item>hello</Item>
            <Item>there</Item>
        </Item>
        <Item xsi:type="SortedSetWrapperOfString">
            <Item>hello</Item>
            <Item>there</Item>
        </Item>
        <Item xsi:type="CollectionWrapperOfLinkedListOfObjectObject">
            <Item xsi:type="CollectionWrapperOfLinkedListOfStringString">
                <Item>hello</Item>
                <Item>there</Item>
            </Item>
        </Item>
    </fm>
</mc>

每个不同的集合类型如何有自己独特的xsi:type