I was trying to create a generic Dictionary
that implements IXmlSerializable
(credit to Charles Feduke).
Here is my trial:
Sub Main()
Dim z As New SerializableDictionary(Of String, String)
z.Add("asdf", "asd")
Console.WriteLine(z.Serialize)
End Sub
Result:
<?xml version="1.0" encoding="utf-16"?><Entry key="asdf" value="asd" />
I placed a breakpoint on top of the WriteXml method and I see that when it stops, the writer contains no data at all, and IMHO it should contain the root element and the xml declaration.
<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.Get开发者_运维问答Schema
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
Why would it contain a root? You aren't adding one in Serialize
, where you create the XmlWriter
... I wonder if you should have something more like (C#, sorry):
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();
}
This uses the regular XmlSerializer
core for things like writing the outer element(s). Alternatively, change Serialize
to include an outer element of your choosing.
Not an answer, but I found 2 bugs in Shimmy's code (thanks by the way) and one gotcha for those trying to use this against .Net 2.0
The bugs are related to each other. The calls to:
WriteData(IsKeyAttributable, writer, True, entry.Key)
WriteData(IsValueAttributable, writer, False, entry.Value)
should be
WriteData(IsKeyAttributable, writer, True, DirectCast(entry.Key, TKey))
WriteData(IsValueAttributable AndAlso IsKeyAttributable, writer, False, CType(entry.Value, TValue))
i.e If the Key is not able to be an XML attribute, then the value cannot be an XML attribute. Also, need to cast the entry.Key and entry.Value to it's TKey/TValue, otherwise the XMLSerializer complains later on. Also
Similarly the call
Dim value = ReadData(Of TValue)(IsValueAttributable, reader, False)
should be
Dim value = ReadData(Of TValue)(IsValueAttributable AndAlso IsKeyAttributable, reader, False)
i.e Agin check that the key is attributale if the value is attributable
And for those targetting .Net runtime 2.0 you will need the GetIsAttributable predicate declared as
Private Shared ReadOnly GetIsAttributable As Predicate(Of Type) = Function(t) DirectCast(AttributableTypes, IList).Contains(t)
精彩评论