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


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

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

8 72

8 ответов:

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

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

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

Я сделал потоковую передачу в строку, потому что это было более удобно для меня, хотя я мог бы ввести сериализацию более низкого уровня для потока, а затем использовать ее для сериализации в строки, но я склонен обобщать только тогда, когда это необходимо (так же, как преждевременная оптимизация является злом, поэтому это преждевременное обобщение...)

использование очень простое:

// 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.

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

Они добавили один в .NET 3.0. Если вы можете, добавьте ссылку на систему.Во время выполнения.Сериализация и поиск системы.XML.XmlDictionary, System.XML.XmlDictionaryReader, и система.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>

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

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

    }
}

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

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

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
    }

}

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