XmlSerializer와 Dictionary 타입
Dictionary 타입("Faster C# Dictionary")에 대해 XmlSerializer를 사용하면,
using System.Xml.Serialization;
internal class Program
{
static void Main(string[] args)
{
MemoryStream ms = new MemoryStream();
Dictionary<string, long> dict = new Dictionary<string, long>()
{
{ "1", 1000 },
{ "2", 2000 },
{ "3", 3000 },
{ "4", 4000 },
};
var formatter = new XmlSerializer(typeof(Dictionary<string, long>));
formatter.Serialize(ms, dict);
ms.Position = 0;
dict = formatter.Deserialize(ms) as Dictionary<string, long>;
foreach (var key in dict.Keys)
{
Console.WriteLine($"{key} = {dict[key]}");
}
}
}
다음과 같은 식의 예외가 발생합니다.
Unhandled exception. System.NotSupportedException: The type System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Int64, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] is not supported because it implements IDictionary.
at System.Xml.Serialization.TypeScope.GetDefaultIndexer(Type type, String memberInfo)
at System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, MemberInfo memberInfo, Boolean directReference)
at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError)
at System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference)
at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type)
at Program.Main(String[] args) in C:\Console1\Console1\Program.cs:line 17
예외 메시지 그대로 XmlSerializer는 IDictionary를 구현한 타입에 대한 직렬화를 지원하지 못합니다. 이에 대한 우회 방법은 2가지입니다.
Why doesn't XmlSerializer support Dictionary?
; https://stackoverflow.com/questions/2911514/why-doesnt-xmlserializer-support-dictionary
정리하면, 1) 직렬화 클래스를 바꾸거나 2) Dictionary 대신 XmlSerializer가 이해할 수 있는 자료 구조를 사용하는 것입니다. 1번 방법의 대표적인 경우가 BinaryFormatter를 사용하는 것인데 이에 대해서는 전에도 한 번 설명한 적이 있습니다.
Dictionary<TKey, TValue>를 deep copy하는 방법
; https://www.sysnet.pe.kr/2/0/11157
반면 2번째 방법도 꽤 괜찮습니다. 즉, "XML Serializable Generic Dictionary"를 사용하는 것인데요,
XML Serializable Generic Dictionary
; https://weblogs.asp.net/pwelter34/444961
XML Serializable Dictionary
; https://learn.microsoft.com/en-us/archive/blogs/amolgote/xml-serializable-dictionary
nullable 문맥을 약간 조정해 다음과 같이 SerializableDictionary를 만들면,
using System.Xml.Serialization;
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue?>, IXmlSerializable where TKey : notnull
{
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema? GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey? key = (TKey?)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue? value = (TValue?)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
if (key != null)
{
this.Add(key, value);
}
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue? value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
#endregion
}
이를 사용하는 클래스 측에서는 다음과 같이 Dictionary를 SerializableDictionary로 바꾸기만 하면 됩니다.
MemoryStream ms = new MemoryStream();
SerializableDictionary<string, long>? dict = new SerializableDictionary<string, long>()
{
{ "1", 1000 },
{ "2", 2000 },
{ "3", 3000 },
{ "4", 4000 },
};
var formatter = new XmlSerializer(typeof(SerializableDictionary<string, long>));
formatter.Serialize(ms, dict);
ms.Position = 0;
dict = formatter.Deserialize(ms) as SerializableDictionary<string, long>;
if (dict != null)
{
foreach (var key in dict.Keys)
{
Console.WriteLine($"{key} = {dict[key]}");
}
}
/* 출력 결과
1 = 1000
2 = 2000
3 = 3000
4 = 4000
*/
(첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]