【问题标题】:IEnumerable(of String) ignored by XmlSerializerIEnumerable(of String) 被 XmlSerializer 忽略
【发布时间】:2019-07-30 15:22:12
【问题描述】:

对于我的一个对象,我需要做一些向后/向前兼容技巧,以便在不同版本共存并共享相同配置文件时每个人都感到高兴。

我尝试的最后一个技巧是创建一个 bridge 集合,该集合使用 IEnumerable(Of String) 将旧格式映射到新格式。我的问题是我无法让XmlSerializer 识别我的自定义集合。我查看了生成代码的序列化程序代码,一切似乎都很好。不管我做什么,只要我使用我的自定义集合ListeCategories 最终会在Unsupported 中。如果我使用基本的List(Of String),它可以工作,但我错过了逻辑。我错过了什么?

我还尝试了一个非通用自定义集合(在第二个代码 sn-p 中注释),但没有更多成功。

这里是主对象和自定义集合的代码。 请注意,XmlSerializer(根据MS Docs)要求的自定义可枚举实现和附加Add 方法。

XmlSerializer 对实现 IEnumerable 或 ICollection 的类进行特殊处理。实现 IEnumerable 的类必须实现采用单个参数的公共 Add 方法。 Add 方法的参数必须与从 GetEnumerator 返回的值的 Current 属性返回的类型相同,或者该类型的基数之一。除了 IEnumerable 之外,实现 ICollection(如 CollectionBase)的类必须具有一个采用整数的公共 Item 索引属性(C# 中的索引器),并且它必须具有一个整数类型的公共 Count 属性。 Add 方法的参数必须与从 Item 属性返回的类型相同,或者是该类型的基数之一。对于实现 ICollection 的类,要序列化的值是从索引的 Item 属性中检索的,而不是通过调用 GetEnumerator。

Public Class OptionsSerializable
    Public Sub New()
    End Sub

    <XmlAnyElement>
    Public Property Unsupported As List(Of XmlElement)

    <System.Xml.Serialization.XmlElement(ElementName:="ListeCategoriesExt")>
    Public ReadOnly Property Categories As List(Of CategoryInfo)

    Public ReadOnly Property ListeCategories As IEnumerable(Of String) = New EnumerableBridge(Of String, CategoryInfo)(_categories, Function(s) New CategoryInfo(s), Function(c) c.SearchTerm)

    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
'Public Class SpecificCollectionBridge
'    Inherits CollectionBridge(Of String, CategoryInfo)

'    Public Sub New(refCollection As IEnumerable(Of CategoryInfo))
'        MyBase.New(refCollection, Function(s) New CategoryInfo(s), Function(c) c.SearchTerm)
'    End Sub

'End Class

<Serializable>
Public Class EnumerableBridge(Of TSource, TTarget)
    Implements IEnumerable(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

    Public Sub Add(item As TSource) 'Implements ICollection(Of TSource).Add
        _refCollection.Add(_converterTo(item))
    End Sub

    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return GetEnumerator()
    End Function

    Public Function IEnumerableOfT_GetEnumerator() As IEnumerator(Of TSource) Implements IEnumerable(Of TSource).GetEnumerator
        Return _refCollection.Select(_converterFrom).GetEnumerator()
    End Function
End Class

【问题讨论】:

    标签: .net vb.net xmlserializer


    【解决方案1】:

    这里有一些问题:

    1. 您的属性ListeCategories 必须声明为实际返回的具体类型,而不是接口。 XmlSerializer 不会序列化声明为接口的属性,即使在预分配时也是如此。

      见:Serializing a List<> exported as an ICollection<> to XML

    2. 您的类型EnumerableBridge(Of TSource, TTarget) 必须直接实现GetEnumerator() As IEnumerator(Of TSource)(使用名称GetEnumerator())并显式实现GetEnumerator() As IEnumerator,使用其他名称。

      在您的课程中,您执行相反的操作并显式实现泛型方法,这会导致一个奇怪的异常并抛出无用的消息:

      [System.InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. EnumerableBridge`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[CategoryInfo, 58be8a1c-a824-4133-9ef8-b2552cccedab, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]] does not implement Add(System.Object).]
      

      问题的演示在这里:https://dotnetfiddle.net/PhaeZc

    3. 您可以从EnumerableBridge() 的无参数构造函数中抛出NotImplementedException(),因为XmlSerializer 实际上不会调用它。

    4. 假设这个问题与您之前的问题 .NET - Is it possible to use both XmlAnyElementAttribute and XmlSerializer.UnknownElement event within the same object 相关,您需要增强 EnumerableBridge.Add(item As TSource) 以检查带有传入 SearchTermCategoryInfo 是否已经反序列化并且已添加,如果是,请不要添加重复项。

      如果您不这样做,那么每次您往返 XML 时,ListeCategoriesExt 的内容都会重复。

      问题的演示在这里:https://dotnetfiddle.net/C0CUV4

    5. 由于Action(Of IList(Of TTarget), TSource)Func(Of TTarget, TSource) 无法正确序列化(通过BinaryFormatter),我建议从EnumerableBridge 中删除&lt;Serializable&gt;

    因此您的EnumerableBridge 应该如下所示:

    ' <Serializable> Removed since _converterFrom and _add are not really serializable
    Public Class EnumerableBridge(Of TSource, TTarget)
        Implements IEnumerable(Of TSource)
    
        Private _refCollection As IList(Of TTarget)
        Private _add As Action(Of IList(Of TTarget), TSource)
        Private _converterFrom As Func(Of TTarget, TSource)
    
        Sub New()
            Throw New NotImplementedException()
        End Sub
    
        Public Sub New(refCollection As IList(Of TTarget), add As Action(Of IList(Of TTarget), TSource), converterFrom As Func(Of TTarget, TSource))
            _refCollection = refCollection
            _add = add
            _converterFrom = converterFrom
        End Sub
    
        Public Sub Add(item As TSource) 'Implements ICollection(Of TSource).Add
            _add(_refCollection, item)
        End Sub
    
        Public Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            Return GetEnumerator()
        End Function
    
        Public Function GetEnumerator() As IEnumerator(Of TSource) Implements IEnumerable(Of TSource).GetEnumerator
            Return _refCollection.Select(_converterFrom).GetEnumerator()
        End Function
    End Class
    

    OptionsSerializable应该修改如下:

    Public Class OptionsSerializable
        Public Sub New()
        End Sub
    
        <XmlAnyElement>
        Public Property Unsupported As List(Of XmlElement)
    
        <System.Xml.Serialization.XmlElement(ElementName:="ListeCategoriesExt")>
        Public ReadOnly Property Categories As List(Of CategoryInfo) = New List(Of CategoryInfo)
    
        Shared Sub AddCategory(Categories as IList(Of CategoryInfo), Name as String)
            If Not Categories.Any(Function(c) c.SearchTerm = Name) Then
                Categories.Add(New CategoryInfo(Name))
            End If
        End Sub
    
        Public ReadOnly Property ListeCategories As EnumerableBridge(Of String, CategoryInfo) = New EnumerableBridge(Of String, CategoryInfo)(_categories, AddressOf AddCategory, Function(c) c.SearchTerm)
    
        ' Remainder unchanged
    

    这里是演示小提琴:https://dotnetfiddle.net/bfYd1l

    【讨论】:

    • 所以问题确实是第 1 点和第 2 点。我确定我之前尝试过,但我想我做了其他组合并错过了这个:P 再次感谢您在理解 XmlSerializer 方面提供的全面帮助!
    猜你喜欢
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-29
    相关资源
    最近更新 更多