Почему в .NET нет XML-сериализуемого словаря?

Мне нужен словарь для сериализации XML. На самом деле, у меня теперь есть две совершенно разные программы, которые нуждаются в этом. Я был довольно удивлен, увидев, что у .NET нет такого. Я задал вопрос в другом месте и получил саркастические ответы. Я не понимаю, почему это глупый вопрос.

Может ли кто-нибудь просветить меня, учитывая, как зависимые различные функции .NET связаны с сериализацией XML, почему нет словаря для XML-сериализации. Надеюсь, вы также можете объяснить, почему некоторые люди считают этот глупый вопрос. Наверное, я должен упустить что-то фундаментальное, и я надеюсь, что вы сможете заполнить пробелы.

Ответ 1

Дело в XML Serialization заключается в том, что это не просто создание потока байтов. Это также о создании XML-схемы, с которой этот поток байтов будет проверяться. В XML-схеме нет хорошего способа представления словаря. Лучшее, что вы можете сделать, это показать, что есть уникальный ключ.

Вы всегда можете создать свою собственную оболочку, например Один способ сериализации словарей.

Ответ 2

Я знаю, что на это был дан ответ раньше, но так как у меня очень сжатый способ (код) для сериализации IDictionary с классом DataContractSerializer (используемый 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, хотя я еще не тестировал его.

ОБНОВЛЕНИЕ:. не работает в .NET Compact Framework (даже не 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.

Надеюсь, что это поможет кому-то!

Ответ 3

Они добавили один в .NET 3.0. Если можно, добавьте ссылку на System.Runtime.Serialization и найдите System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader и System.Xml.XmlDictionaryWriter.

Я бы согласился с тем, что он не находится в особенно уязвимом месте.

Ответ 4

Создайте свой собственный:-), функция readonly - это бонус, но если вам нужен ключ, отличный от строки, то класс нуждается в некоторых модификациях...

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

    }
}

Ответ 5

Используйте 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>

Ответ 6

Это моя реализация.

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
    }

}

Ответ 7

Общий помощник для быстрого добавления 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

И удобный сериализуемый общий словарь:

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

Ответ 8

Я знаю, что теперь это было сделано до смерти, но вот мой вклад. Я взял хорошие бит из решений от @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, и все готово.