为什么 .NET 中没有 XML 可序列化字典?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1124597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why isn't there an XML-serializable dictionary in .NET?
提问by serialhobbyist
I need an XML-serializable dictionary. Actually, I now have two quite different programs that need one. I was rather surprised to see that .NET doesn't have one. I asked the question elsewhere and got sarcastic responses. I don't understand why it's a stupid question.
我需要一个 XML 可序列化的字典。实际上,我现在有两个完全不同的程序需要一个。我很惊讶地看到 .NET 没有。我在别处问了这个问题,得到了讽刺的回答。我不明白为什么这是一个愚蠢的问题。
Can someone enlighten me, given how dependent various .NET features are on XML serialization, why there isn't an XML-serializable dictionary. Hopefully, you can also explain why some people consider that a daft question. I guess I must be missing something fundamental and I'm hoping you'll be able to fill in the gaps.
考虑到各种 .NET 功能对 XML 序列化的依赖程度,有人可以启发我,为什么没有 XML 可序列化字典。希望您也能解释为什么有些人认为这是一个愚蠢的问题。我想我一定遗漏了一些基本的东西,我希望你能够填补空白。
采纳答案by John Saunders
The thing about XML Serialization is that it's not just about creating a stream of bytes. It's also about creating an XML Schema that this stream of bytes would validate against. There's no good way in XML Schema to represent a dictionary. The best you could do is to show that there's a unique key.
XML 序列化的问题在于它不仅仅是创建字节流。它还涉及创建一个 XML 模式,该字节流将根据该模式进行验证。XML Schema 中没有表示字典的好方法。你能做的最好的事情就是证明有一个唯一的键。
You can always create your own wrapper, for instance One Way to Serialize Dictionaries.
您始终可以创建自己的包装器,例如One Way to Serialize Dictionaries。
回答by Loudenvier
I know this has been answered before, but since I have a very concise way (code) for doing IDictionary serialization with the DataContractSerializer class (used by WCF, but could and should be used anywhere) I couldn't resist contributing it here:
我知道之前已经回答过这个问题,但是由于我有一个非常简洁的方法(代码)来使用 DataContractSerializer 类(由 WCF 使用,但可以而且应该在任何地方使用)进行 IDictionary 序列化,我无法抗拒在这里贡献它:
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);
}
}
}
This works perfectly in .NET 4 and should also work in .NET 3.5, although I didn't test it yet.
这在 .NET 4 中完美运行,也应该在 .NET 3.5 中运行,尽管我还没有测试它。
UPDATE:It doesn'twork in .NET Compact Framework (not even NETCF 3.7 for Windows Phone 7) as the DataContractSerializeris not supported!
更新:它不作为(为Windows Phone 7甚至没有NETCF 3.7)在.NET Compact Framework中的工作DataContractSerializer不支持!
I did the streaming to string because it was more convenient to me, although I could have introduced a lower-level serialization to Stream and then used it to serialize to strings, but I tend to generalize only when needed (just like premature optimization is evil, so it is premature generalization...)
我将流式传输到字符串是因为它对我来说更方便,虽然我可以向 Stream 引入较低级别的序列化,然后使用它来序列化为字符串,但我倾向于仅在需要时进行泛化(就像过早的优化是邪恶的,所以这是过早的概括......)
Usage is very simple:
用法很简单:
// 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 will be a verbatim copy of myDict.
myDictCopy 将是 myDict 的逐字副本。
You'll also notice that the generic methods provided will be able to serialize any type (to the best of my knowledge) since it is not limited to IDictionary interfaces, it can be really any generic type T.
您还会注意到提供的泛型方法将能够序列化任何类型(据我所知),因为它不仅限于 IDictionary 接口,它实际上可以是任何泛型类型 T。
Hope it helps someone out there!
希望它可以帮助那里的人!
回答by Joe Chung
They added one in .NET 3.0. If you can, add a reference to System.Runtime.Serialization and look for System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader, and System.Xml.XmlDictionaryWriter.
他们在 .NET 3.0 中添加了一个。如果可以,请添加对 System.Runtime.Serialization 的引用并查找 System.Xml.XmlDictionary、System.Xml.XmlDictionaryReader 和 System.Xml.XmlDictionaryWriter。
I would agree that it is not in a particularly discoverable place.
我同意它不在一个特别容易发现的地方。
回答by Joe Chung
Use the DataContractSerializer! See the sample below.
使用 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;
}
}
}
The above code produces the following xml:
上面的代码生成以下 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>
回答by bang
Create one of your own :-), the readonly feature is bonus but if you need a key other than a string then the class needs some modifications...
创建一个你自己的:-),只读功能是额外的,但是如果你需要一个不是字符串的键,那么这个类需要一些修改......
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();
}
}
}
}
回答by Hyman
A generic helper to quickly add IXmlSerializable to any (existing) Dictionary without using inheritance:
在不使用继承的情况下快速将 IXmlSerializable 添加到任何(现有)字典的通用助手:
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
And a convenient serializable generic dictionary:
还有一个方便的可序列化通用字典:
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
回答by Steztric
I know this has been done to death now, but here is my contribution. I took the good bits from the solutions from @Loudenvier and @Hyman and wrote my own serialisable (sorry, I'm British) dictionary class.
我知道这已经完成了,但这是我的贡献。我从@Loudenvier 和@Hyman 的解决方案中汲取了一些优点,并编写了我自己的可序列化(对不起,我是英国人)字典类。
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;
}
}
I like this approach because you won't have to explicitly serialise or deserialise anything, just pump the whole class hierarchy through an XmlSerializer and you're done.
我喜欢这种方法,因为您不必显式序列化或反序列化任何内容,只需通过 XmlSerializer 泵送整个类层次结构即可完成。
回答by Tümay Tuzcu
This is my implementation.
这是我的实现。
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
}
}

