为什么在.NET 中没有一个可 XML 序列化的字典?

我需要一个可 XML 序列化的字典。事实上,我现在有两个完全不同的程序需要一个。看到这个我很惊讶。NET 没有。

鉴于各种.NET 特性对 XML 序列化的依赖程度如此之高,有人能告诉我为什么没有可序列化 XML 的字典吗?

47152 次浏览

XML 序列化不仅仅是创建字节流。它还涉及到创建一个 XMLSchema,以便根据该字节流进行验证。在 XMLSchema 中没有表示字典的好方法。你能做的就是展示一把独一无二的钥匙。

您总是可以创建自己的包装器,例如 词典序列化的一种方法

创建一个你自己的: ——) ,只读特性是额外的,但是如果你需要一个关键字而不是一个字符串,然后类需要一些修改..。

namespace MyNameSpace
{
[XmlRoot("SerializableDictionary")]
public class SerializableDictionary : Dictionary<String, Object>, IXmlSerializable
{
internal Boolean _ReadOnly = false;
public Boolean ReadOnly
{
get
{
return this._ReadOnly;
}


set
{
this.CheckReadOnly();
this._ReadOnly = value;
}
}


public new Object this[String key]
{
get
{
Object value;


return this.TryGetValue(key, out value) ? value : null;
}


set
{
this.CheckReadOnly();


if(value != null)
{
base[key] = value;
}
else
{
this.Remove(key);
}
}
}


internal void CheckReadOnly()
{
if(this._ReadOnly)
{
throw new Exception("Collection is read only");
}
}


public new void Clear()
{
this.CheckReadOnly();


base.Clear();
}


public new void Add(String key, Object value)
{
this.CheckReadOnly();


base.Add(key, value);
}


public new void Remove(String key)
{
this.CheckReadOnly();


base.Remove(key);
}


public XmlSchema GetSchema()
{
return null;
}


public void ReadXml(XmlReader reader)
{
Boolean wasEmpty = reader.IsEmptyElement;


reader.Read();


if(wasEmpty)
{
return;
}


while(reader.NodeType != XmlNodeType.EndElement)
{
if(reader.Name == "Item")
{
String key = reader.GetAttribute("Key");
Type type = Type.GetType(reader.GetAttribute("TypeName"));


reader.Read();
if(type != null)
{
this.Add(key, new XmlSerializer(type).Deserialize(reader));
}
else
{
reader.Skip();
}
reader.ReadEndElement();


reader.MoveToContent();
}
else
{
reader.ReadToFollowing("Item");
}


reader.ReadEndElement();
}


public void WriteXml(XmlWriter writer)
{
foreach(KeyValuePair<String, Object> item in this)
{
writer.WriteStartElement("Item");
writer.WriteAttributeString("Key", item.Key);
writer.WriteAttributeString("TypeName", item.Value.GetType().AssemblyQualifiedName);


new XmlSerializer(item.Value.GetType()).Serialize(writer, item.Value);


writer.WriteEndElement();
}
}


}
}

他们加了一个。NET 3.0.如果可以,请添加对 System 的引用。运行时间。序列化和查找系统。Xml.XmlDictionary,System.Xml.XmlDictionaryReader 和 System。Xml.XmlDictionaryWriter.

我同意它不在一个特别容易被发现的地方。

使用 DataContractSerializer! 请参阅下面的示例。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml;


namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Value = 1;


B b = new B();
b.Value = "SomeValue";


Dictionary<A, B> d = new Dictionary<A,B>();
d.Add(a, b);
DataContractSerializer dcs = new DataContractSerializer(typeof(Dictionary<A, B>));
StringBuilder sb = new StringBuilder();
using (XmlWriter xw = XmlWriter.Create(sb))
{
dcs.WriteObject(xw, d);
}
string xml = sb.ToString();
}
}


public class A
{
public int Value
{
get;
set;
}
}


public class B
{
public string Value
{
get;
set;
}
}
}

上面的代码生成以下 xml:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfABHtQdUIlS>
<Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
<d3p1:Value>1</d3p1:Value>
</Key>
<Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
<d3p1:Value>SomeValue</d3p1:Value>
</Value>
</KeyValueOfABHtQdUIlS>
</ArrayOfKeyValueOfABHtQdUIlS>

这是我的实施方案。

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;


namespace Rubik.Staging
{
[XmlSchemaProvider("GetInternalSchema")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
#region IXmlSerializable Members


private const string ns = "http://www.rubik.com.tr/staging";


public static XmlQualifiedName GetInternalSchema(XmlSchemaSet xs)
{
bool keyIsSimple = (typeof(TKey).IsPrimitive || typeof(TKey) == typeof(string));
bool valueIsSimple = (typeof(TValue).IsPrimitive || typeof(TValue) == typeof(string));


XmlSchemas schemas = new XmlSchemas();


XmlReflectionImporter importer = new XmlReflectionImporter(ns);
importer.IncludeType(typeof(TKey));
importer.IncludeType(typeof(TValue));


XmlTypeMapping keyMapping = importer.ImportTypeMapping(typeof(TKey));
XmlTypeMapping valueMapping = importer.ImportTypeMapping(typeof(TValue));


XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);


if(!keyIsSimple)
exporter.ExportTypeMapping(keyMapping);
if(!valueIsSimple)
exporter.ExportTypeMapping(valueMapping);


XmlSchema schema = (schemas.Count == 0 ? new XmlSchema() : schemas[0]);


schema.TargetNamespace = ns;
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.Name = "DictionaryOf" + keyMapping.XsdTypeName + "And" + valueMapping.XsdTypeName;
XmlSchemaSequence sequence = new XmlSchemaSequence();
XmlSchemaElement item = new XmlSchemaElement();
item.Name = "Item";


XmlSchemaComplexType itemType = new XmlSchemaComplexType();
XmlSchemaSequence itemSequence = new XmlSchemaSequence();


XmlSchemaElement keyElement = new XmlSchemaElement();


keyElement.Name = "Key";
keyElement.MaxOccurs = 1;
keyElement.MinOccurs = 1;


XmlSchemaComplexType keyType = new XmlSchemaComplexType();
XmlSchemaSequence keySequence = new XmlSchemaSequence();
XmlSchemaElement keyValueElement = new XmlSchemaElement();
keyValueElement.Name = keyMapping.ElementName;
keyValueElement.SchemaTypeName = new XmlQualifiedName(keyMapping.XsdTypeName, keyMapping.XsdTypeNamespace);
keyValueElement.MinOccurs = 1;
keyValueElement.MaxOccurs = 1;
keySequence.Items.Add(keyValueElement);
keyType.Particle = keySequence;
keyElement.SchemaType = keyType;
itemSequence.Items.Add(keyElement);




XmlSchemaElement valueElement = new XmlSchemaElement();


valueElement.Name = "Value";
valueElement.MaxOccurs = 1;
valueElement.MinOccurs = 1;


XmlSchemaComplexType valueType = new XmlSchemaComplexType();
XmlSchemaSequence valueSequence = new XmlSchemaSequence();
XmlSchemaElement valueValueElement = new XmlSchemaElement();
valueValueElement.Name = valueMapping.ElementName;
valueValueElement.SchemaTypeName = new XmlQualifiedName(valueMapping.XsdTypeName, valueMapping.XsdTypeNamespace);
valueValueElement.MinOccurs = 1;
valueValueElement.MaxOccurs = 1;
valueSequence.Items.Add(valueValueElement);
valueType.Particle = valueSequence;
valueElement.SchemaType = valueType;
itemSequence.Items.Add(valueElement);
itemType.Particle = itemSequence;
item.SchemaType = itemType;
sequence.Items.Add(item);
type.Particle = sequence;
schema.Items.Add(type);


xs.XmlResolver = new XmlUrlResolver();
xs.Add(schema);


return new XmlQualifiedName(type.Name, ns);
}










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();


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


#region IXmlSerializable Members


public XmlSchema GetSchema()
{
return null;
}


#endregion
}


}

我知道以前已经有人回答过这个问题,但是因为我有一个非常简洁的方法(代码)来用 DataContractSerializer 类进行 IDictionary 序列化(由 WCF 使用,但是可以并且应该在任何地方使用) ,所以我忍不住在这里贡献它:

public static class SerializationExtensions
{
public static string Serialize<T>(this T obj)
{
var serializer = new DataContractSerializer(obj.GetType());
using (var writer = new StringWriter())
using (var stm = new XmlTextWriter(writer))
{
serializer.WriteObject(stm, obj);
return writer.ToString();
}
}
public static T Deserialize<T>(this string serialized)
{
var serializer = new DataContractSerializer(typeof(T));
using (var reader = new StringReader(serialized))
using (var stm = new XmlTextReader(reader))
{
return (T)serializer.ReadObject(stm);
}
}
}

这在.NET 4中可以很好地工作,在.NET 3.5中也应该可以工作,尽管我还没有测试它。

更新: It 没有 work in。NET 紧凑框架(甚至不是 NETCF 3.7的 Windows Phone 7) ,因为 DataContractSerializer不受支持!

我做了字符串串流化,因为它对我来说更方便,虽然我可以引入一个较低级别的串行化到 Stream,然后用它来串行化到字符串,但我倾向于只在需要的时候泛化(就像过早优化是邪恶的,所以它是过早泛化...)

用法很简单:

// dictionary to serialize to string
Dictionary<string, object> myDict = new Dictionary<string, object>();
// add items to the dictionary...
myDict.Add(...);
// serialization is straight-forward
string serialized = myDict.Serialize();
...
// deserialization is just as simple
Dictionary<string, object> myDictCopy =
serialized.Deserialize<Dictionary<string,object>>();

MyDictCopy 将是 myDict 的一个逐字副本。

您还会注意到,所提供的泛型方法将能够序列化任何类型(据我所知) ,因为它不限于 IDictionary 接口,它实际上可以是任何泛型类型 T。

希望能帮到别人!

一个通用帮助器,可以在不使用继承的情况下快速将 IXmlSerializer 添加到任何(现有的) Dictionary:

using System.Xml;
using System.Xml.Serialization;
using System.Collections.Generic;


namespace GameSpace {


public class XmlSerializerForDictionary {


public struct Pair<TKey,TValue> {


public TKey Key;
public TValue Value;


public Pair(KeyValuePair<TKey,TValue> pair) {
Key = pair.Key;
Value = pair.Value;
}//method


}//struct


public static void WriteXml<TKey,TValue>(XmlWriter writer, IDictionary<TKey,TValue> dict) {


var list = new List<Pair<TKey,TValue>>(dict.Count);


foreach (var pair in dict) {
list.Add(new Pair<TKey,TValue>(pair));
}//foreach


var serializer = new XmlSerializer(list.GetType());
serializer.Serialize(writer, list);


}//method


public static void ReadXml<TKey, TValue>(XmlReader reader, IDictionary<TKey, TValue> dict) {


reader.Read();


var serializer = new XmlSerializer(typeof(List<Pair<TKey,TValue>>));
var list = (List<Pair<TKey,TValue>>)serializer.Deserialize(reader);


foreach (var pair in list) {
dict.Add(pair.Key, pair.Value);
}//foreach


reader.Read();


}//method


}//class


}//namespace

还有一个方便的可串行化的通用字典:

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Collections.Generic;


namespace GameSpace {


public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, IXmlSerializable {


public virtual void WriteXml(XmlWriter writer) {
XmlSerializerForDictionary.WriteXml(writer, this);
}//method


public virtual void ReadXml(XmlReader reader) {
XmlSerializerForDictionary.ReadXml(reader, this);
}//method


public virtual XmlSchema GetSchema() {
return null;
}//method


}//class


}//namespace

我知道这已经做到死了,但这是我的贡献。我从@Loudenvier 和@Jack 的解决方案中选取了好的部分,然后写了我自己的串行化(对不起,我是英国人)字典类。

public class SerialisableDictionary<T1, T2> : Dictionary<T1, T2>, IXmlSerializable
{
private static DataContractSerializer serializer =
new DataContractSerializer(typeof(Dictionary<T1, T2>));


public void WriteXml(XmlWriter writer)
{
serializer.WriteObject(writer, this);
}


public void ReadXml(XmlReader reader)
{
Dictionary<T1, T2> deserialised =
(Dictionary<T1, T2>)serializer.ReadObject(reader);


foreach(KeyValuePair<T1, T2> kvp in deserialised)
{
Add(kvp.Key, kvp.Value);
}
}


public XmlSchema GetSchema()
{
return null;
}
}

我喜欢这种方法,因为您不必显式序列化或反序列化任何东西,只需通过 XmlSerializer 打开整个类层次结构,就可以完成了。