XmlSerializer忽略IEnumerable(字符串)

时间:2019-07-30 15:22:12

标签: .net vb.net xmlserializer

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

我尝试的最后一个技巧是拥有一个 bridge 集合,该集合使用IEnumerable(Of String)将旧格式映射为新格式。我的问题是我无法XmlSerializer来识别我的自定义集合。我看着序列化程序代码生成的代码,一切似乎都很好。无论我做什么,只要使用我的自定义集合ListeCategories都以Unsupported结尾。如果我使用基本的List(Of String),它可以工作,但是我错过了逻辑。我想念什么?

我还尝试了一个非通用的自定义集合(在第二个代码段中进行了注释),但没有成功。

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

  

XmlSerializer对实现IEnumerable或ICollection的类给予特殊待遇。实现IEnumerable的类必须实现采用单个参数的公共Add方法。 Add方法的参数必须与GetEnumerator返回的值的Current属性所返回的类型相同,或该类型的基数之一。除了IEnumerable之外,实现ICollection的类(例如CollectionBase)还必须具有带整数的公共Item索引属性(在C#中为indexer),并且它必须具有整数类型的公共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

1 个答案:

答案 0 :(得分:3)

您在这里遇到一些问题:

  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. 您可以从NotImplementedException()的无参数构造函数中抛出EnumerableBridge(),因为XmlSerializer实际上不会调用它。

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

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

    在此处演示问题的小提琴:https://dotnetfiddle.net/C0CUV4

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

因此,您的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

相关问题