XmlSerializer无法识别自定义IEnumerable(Of String)集合的问题排查
问题分析与解决方案
你遇到的核心问题是XmlSerializer无法识别自定义的EnumerableBridge集合类,哪怕已经按照MS Docs要求实现了Add方法。这主要是因为自定义集合只实现了IEnumerable(Of TSource),而XmlSerializer对自定义集合的序列化/反序列化有更严格的契约要求——尤其是需要双向兼容读写XML的时候。
核心原因拆解
XmlSerializer处理集合类型的规则细节很容易被忽略:
- 仅实现
IEnumerable(Of T)和公共Add方法是不够的,反序列化阶段(从XML读取到对象)需要更多元数据确认集合的可操作性。 - 你的
EnumerableBridge没有实现ICollection(Of TSource)接口,XmlSerializer对这类“轻量”Enumerable的支持有限,更倾向于处理实现了ICollection或ICollection(Of T)的类——这些接口提供了必要的集合操作契约(比如Count、元素操作方法等)。 - 另外,
ListeCategories是只读属性,虽然返回了集合实例,但XmlSerializer默认对只读集合属性的支持有限,除非该集合是XmlSerializer内置支持的类型(比如List(Of String))。
修正方案
我们需要让EnumerableBridge实现ICollection(Of TSource),完善必要的接口成员,同时调整属性标记让XmlSerializer正确识别它。
1. 改造EnumerableBridge实现ICollection(Of TSource)
<Serializable> Public Class EnumerableBridge(Of TSource, TTarget) Implements IEnumerable(Of TSource), ICollection(Of TSource) Private _refCollection As IList(Of TTarget) Private _converterTo As Func(Of TSource, TTarget) Private _converterFrom As Func(Of TTarget, TSource) Public Sub New() End Sub Public Sub New(refCollection As IList(Of TTarget), converterTo As Func(Of TSource, TTarget), converterFrom As Func(Of TTarget, TSource)) _refCollection = refCollection _converterTo = converterTo _converterFrom = converterFrom End Sub ' 显式实现ICollection(Of TSource)的Add方法 Public Sub Add(item As TSource) Implements ICollection(Of TSource).Add _refCollection.Add(_converterTo(item)) End Sub ' 实现ICollection的必要只读属性 Public ReadOnly Property Count As Integer Implements ICollection(Of TSource).Count Get Return _refCollection.Count End Get End Property Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of TSource).IsReadOnly Get Return _refCollection.IsReadOnly End Get End Property ' 实现ICollection的其他操作方法 Public Sub Clear() Implements ICollection(Of TSource).Clear _refCollection.Clear() End Sub Public Function Contains(item As TSource) As Boolean Implements ICollection(Of TSource).Contains Dim targetItem = _converterTo(item) Return _refCollection.Contains(targetItem) End Function Public Sub CopyTo(array As TSource(), arrayIndex As Integer) Implements ICollection(Of TSource).CopyTo For Each item In _refCollection.Select(_converterFrom) array(arrayIndex) = item arrayIndex += 1 Next End Sub Public Function Remove(item As TSource) As Boolean Implements ICollection(Of TSource).Remove Dim targetItem = _converterTo(item) Return _refCollection.Remove(targetItem) End Function ' 实现IEnumerable的枚举器 Public Function GetEnumerator() As IEnumerator(Of TSource) Implements IEnumerable(Of TSource).GetEnumerator Return _refCollection.Select(_converterFrom).GetEnumerator() End Function Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return GetEnumerator() End Function End Class
2. 调整OptionsSerializable中的ListeCategories属性
XmlSerializer需要明确知道集合的具体类型(而非接口),同时通过属性标记告知序列化规则:
Public Class OptionsSerializable Public Sub New() End Sub <XmlAnyElement> Public Property Unsupported As List(Of XmlElement) <XmlElement(ElementName:="ListeCategoriesExt")> Public ReadOnly Property Categories As List(Of CategoryInfo) ' 修改为具体集合类型,并添加XmlArray标记明确序列化规则 <XmlArray("ListeCategories")> Public ReadOnly Property ListeCategories As EnumerableBridge(Of String, CategoryInfo) Get Return New EnumerableBridge(Of String, CategoryInfo)(Categories, Function(s) New CategoryInfo(s), Function(c) c.SearchTerm) End Get End Property ' 其余属性保持不变... Public Property ServeurExchangeUrl As String Public Property VersionExchange As String Public Property AdresseBoitePartager As New List(Of String) Public Property GroupingMasks As New List(Of String) Public Property TFSLinks As New XmlSerializableDictionary(Of String, String) Public Property EstAlertageActif As Boolean Public Property Laps As Integer Public Property NbErreursMaximum As Integer Public Property DerniereVerification As Date Public Property ListeEnvoi As String Public Property DernierEnvoi As Date Public Property LogonDernierEnvoi As String Public Property IntervalEnvoiMinimum As Integer End Class
关键说明
- 实现ICollection(Of T)的必要性:XmlSerializer依赖
ICollection接口的成员确认集合的可操作性,比如Count用于确定序列化元素数量,Add用于反序列化时添加元素。 - 具体类型而非接口:XmlSerializer需要知道集合的具体类型才能实例化对象进行反序列化,不能仅返回
IEnumerable(Of String)接口。 - 属性标记的作用:
XmlArray明确告知XmlSerializer该属性是集合类型,会生成对应的父节点和子节点结构。
这样修改后,XmlSerializer就能正确识别你的ListeCategories集合,同时保留新旧格式的转换逻辑了。
内容的提问来源于stack exchange,提问作者bkqc
相关产品推荐
相关产品推荐

