Проблема с IXmlSerializable Dictionary

Я пытался создать общий Dictionary, который реализует IXmlSerializable (кредит на Чарльз Федуке).

Вот мое испытание:

Sub Main()
    Dim z As New SerializableDictionary(Of String, String)
    z.Add("asdf", "asd")
    Console.WriteLine(z.Serialize)
End Sub

Результат:

<?xml version="1.0" encoding="utf-16"?><Entry key="asdf" value="asd" />

Я поместил точку останова поверх метода WriteXml и вижу, что, когда он останавливается, писатель вообще не содержит данных, и ИМХО он должен содержать корневой элемент и объявление xml.


<Serializable()> _
Public Class SerializableDictionary(Of TKey, TValue) : Inherits Dictionary(Of TKey, TValue) : Implements IXmlSerializable
    Private Const EntryString As String = "Entry"
    Private Const KeyString As String = "key"
    Private Const ValueString As String = "value"
    Private Shared ReadOnly AttributableTypes As Type() = New Type() {GetType(Boolean), GetType(Byte), GetType(Char), GetType(DateTime), GetType(Decimal), GetType(Double), GetType([Enum]), GetType(Guid), GetType(Int16), GetType(Int32), GetType(Int64), GetType(SByte), GetType(Single), GetType(String), GetType(TimeSpan), GetType(UInt16), GetType(UInt32), GetType(UInt64)}
    Private Shared ReadOnly GetIsAttributable As Predicate(Of Type) = Function(t) AttributableTypes.Contains(t)
    Private Shared ReadOnly IsKeyAttributable As Boolean = GetIsAttributable(GetType(TKey))
    Private Shared ReadOnly IsValueAttributable As Boolean = GetIsAttributable(GetType(TValue))
    Private Shared ReadOnly GetElementName As Func(Of Boolean, String) = Function(isKey) If(isKey, KeyString, ValueString)

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub WriteXml(ByVal writer As XmlWriter) Implements IXmlSerializable.WriteXml
        For Each entry In Me
            writer.WriteStartElement(EntryString)

            WriteData(IsKeyAttributable, writer, True, entry.Key)
            WriteData(IsValueAttributable, writer, False, entry.Value)

            writer.WriteEndElement()
        Next
    End Sub

    Private Sub WriteData(Of T)(ByVal attributable As Boolean, ByVal writer As XmlWriter, ByVal isKey As Boolean, ByVal value As T)
        Dim name = GetElementName(isKey)

        If attributable Then
            writer.WriteAttributeString(name, value.ToString)
        Else
            Dim serializer As New XmlSerializer(GetType(T))
            writer.WriteStartElement(name)
            serializer.Serialize(writer, value)
            writer.WriteEndElement()
        End If
    End Sub

    Public Sub ReadXml(ByVal reader As XmlReader) Implements IXmlSerializable.ReadXml
        Dim empty = reader.IsEmptyElement

        reader.Read()
        If empty Then Exit Sub

        Clear()

        While reader.NodeType <> XmlNodeType.EndElement
            While reader.NodeType = XmlNodeType.Whitespace
                reader.Read()

                Dim key = ReadData(Of TKey)(IsKeyAttributable, reader, True)
                Dim value = ReadData(Of TValue)(IsValueAttributable, reader, False)

                Add(key, value)

                If Not IsKeyAttributable AndAlso Not IsValueAttributable Then reader.ReadEndElement() Else reader.Read()

                While reader.NodeType = XmlNodeType.Whitespace
                    reader.Read()
                End While
            End While

            reader.ReadEndElement()
        End While
    End Sub

    Private Function ReadData(Of T)(ByVal attributable As Boolean, ByVal reader As XmlReader, ByVal isKey As Boolean) As T
        Dim name = GetElementName(isKey)
        Dim type = GetType(T)

        If attributable Then
            Return Convert.ChangeType(reader.GetAttribute(name), type)
        Else
            Dim serializer As New XmlSerializer(type)

            While reader.Name <> name
                reader.Read()
            End While

            reader.ReadStartElement(name)
            Dim value = serializer.Deserialize(reader)
            reader.ReadEndElement()

            Return value
        End If
    End Function

    Public Shared Function Serialize(ByVal dictionary As SerializableDictionary(Of TKey, TValue)) As String
        Dim sb As New StringBuilder(1024)
        Dim sw As New StringWriter(sb)
        Dim xs As New XmlSerializer(GetType(SerializableDictionary(Of TKey, TValue)))

        xs.Serialize(sw, dictionary)
        sw.Dispose()
        Return sb.ToString
    End Function

    Public Shared Function Deserialize(ByVal xml As String) As SerializableDictionary(Of TKey, TValue)
        Dim xs As New XmlSerializer(GetType(SerializableDictionary(Of TKey, TValue)))
        Dim xr As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
        xr.Close()
              Return xs.Deserialize(xr)
    End Function

    Public Function Serialize() As String
        Dim sb As New StringBuilder
        Dim xw = XmlWriter.Create(sb)
        WriteXml(xw)
        xw.Close()
        Return sb.ToString
    End Function

    Public Sub Parse(ByVal xml As String)
        Dim xr As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
        ReadXml(xr)
        xr.Close()
    End Sub

End Class

person Shimmy Weitzhandler    schedule 19.04.2010    source источник


Ответы (2)


Почему он должен содержать корень? Вы не добавляете его в Serialize, где вы создаете _2 _... Интересно, нужно ли вам иметь что-то еще вроде (C #, извините):

public string Serialize() {
    StringBuilder sb = new StringBuilder();
    XmlSerializer ser = new XmlSerializer(
        typeof(SerializableDictionary<TKey, TValue>));
    using (XmlWriter writer = XmlWriter.Create(sb)) {
        ser.Serialize(writer, this);
    }
    return sb.ToString();
}

Это использует обычное ядро ​​XmlSerializer для таких вещей, как запись внешнего элемента (ов). Или измените Serialize, чтобы включить внешний элемент по вашему выбору.

person Marc Gravell    schedule 19.04.2010
comment
Мне также нужна помощь с методом Parse, поскольку Deserialize возвращает новый объект, я хочу «воссоздать» текущий экземпляр, используя его собственный ReadXml. - person Shimmy Weitzhandler; 19.04.2010
comment
@Shimmy - а где это сейчас ломается? Какой там вопрос / проблема? - person Marc Gravell; 19.04.2010
comment
Нет, первая проблема была решена с помощью вашего фрагмента, другая проблема в том, что я хочу иметь функцию Parse, которая очищает текущую коллекцию и загружает элементы из строкового представления сериализованного экземпляра. - person Shimmy Weitzhandler; 19.04.2010
comment
Также в отношении этого вопроса: stackoverflow.com/questions/2663836/, я смог установить класс, но когда я пытаюсь установить значение по умолчанию, он говорит: Свойство 'Настройка' не может быть создан из его значения по умолчанию. Сообщение об ошибке: Ошибка в XML-документе (1, 41). - person Shimmy Weitzhandler; 19.04.2010
comment
Для согласованности вы должны сделать Parse статическим и вернуть новый экземпляр класса, методом которого Parse является. (Вы часто видите это в .NET Fx.) Если вам нужен метод экземпляра, подобный Parse, вы должны использовать конструктор, который принимает строковый параметр. - person cfeduke; 20.04.2010
comment
В ПОРЯДКЕ. Я согласен, спасибо за это. но я все еще хочу иметь метод ReadXml (sring xml) (метод экземпляра) - person Shimmy Weitzhandler; 21.04.2010

Не ответ, но я обнаружил 2 ошибки в коде Шимми (кстати, спасибо) и одну ошибку для тех, кто пытается использовать это против .Net 2.0.

Ошибки связаны друг с другом. Звонки:

WriteData(IsKeyAttributable, writer, True, entry.Key)
WriteData(IsValueAttributable, writer, False, entry.Value)

должно быть

WriteData(IsKeyAttributable, writer, True, DirectCast(entry.Key, TKey))
WriteData(IsValueAttributable  AndAlso IsKeyAttributable, writer, False, CType(entry.Value, TValue))

Т.е. если ключ не может быть атрибутом XML, тогда значение не может быть атрибутом XML. Кроме того, необходимо преобразовать entry.Key и entry.Value в TKey / TValue, иначе XMLSerializer позже пожалуется. Также

Аналогично призыв

Dim value = ReadData(Of TValue)(IsValueAttributable, reader, False)

должно быть

Dim value = ReadData(Of TValue)(IsValueAttributable AndAlso IsKeyAttributable, reader, False)

т.е. сначала проверьте, является ли ключ атрибутивным, если значение атрибутируется

А для тех, кто нацелен на .Net runtime 2.0, вам понадобится предикат GetIsAttributable, объявленный как

Private Shared ReadOnly GetIsAttributable As Predicate(Of Type) = Function(t) DirectCast(AttributableTypes, IList).Contains(t)
person Steve Downing    schedule 04.06.2010