我可以重现该问题。这是因为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<string> 使用相同的xsi:type 多态名称"ArrayOfString",当它发现可能遇到两者的情况时,它会抛出异常,因为它无法区分他们。
为什么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。