【问题标题】:XmlInclude : List and arrayXmlInclude :列表和数组
【发布时间】:2016-03-30 12:03:29
【问题描述】:

我有一个变量为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年以来一直没有答案。

【问题讨论】:

  • 我已经尝试了您的建议并看到了您的问题。包括它序列化的数组,包括它序列化的列表,但两者都包括,但它不包括。您无法控制可以添加到 MyClass 的内容吗?在我看来,只允许列表将是有益的。值得注意的是,当它同时序列化 List 和 Array 时,它会产生相同的 xml - 所以你可能会遇到冲突,因为这两个包含基本上都包含相同的东西
  • 你是否关心在你的类往返之后,数组是否被转换为列表,反之亦然?或者您是否需要能够往返您的课程并保持数组/列表类型的保真度?

标签: c# arrays list xml-serialization xmlserializer


【解决方案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>

由于序列化程序对string[]List&lt;string&gt; 使用相同的xsi:type 多态名称"ArrayOfString",当它发现可能遇到两者的情况时,它会抛出异常,因为它无法区分他们。

为什么XmlSerializer 对两者使用相同的名称?我只能猜测它可以交换通过序列化不同集合类型(例如从List&lt;TElement&gt;SortedSet&lt;TElement&gt;)创建的 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

【讨论】:

    猜你喜欢
    • 2010-09-27
    • 1970-01-01
    • 2012-08-24
    • 2012-02-14
    • 1970-01-01
    • 2016-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多