Вопрос по .net, dictionary, xml-serialization, appsettings – Почему в .NET нет XML-сериализуемого словаря?

73

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

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

Как вы думаете, какие современные функции в .NET зависят от сериализации XML? Файлы конфигурации не используют сериализацию, а веб-сервисы ASMX предназначены только для устаревшего использования. (перенесено в комментарий от ответа) John Saunders
Вопрос неверный, потому что причина и следствие неверны. Должно быть, "почемуXmlSerializer не может сериализовать словари & quot ;? Потому что есть много способов сделать сериализацию XML в .NET, и большинство из них просто отлично сериализуют словари (DataContractSerializer, SoapFormatter ...). Pavel Minaev
Я предполагаю, что вы не исследовали XmlDictionaryWriter.CreateDictionaryWriter ... или 100 других способов сериализации словарей в .NET (некоторые из них являются встроенными). ... и зачем тебе словарь? Я всегда находил объекты со строгой типизацией для лучшей работы, почему бы просто не реализовать класс с [DataContract] и IExtensibleDataObject? BrainSlugs83

Ваш Ответ

8   ответов
2

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

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

UPDATE: Этоdoesn't работать в .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.

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

Работает отлично! Другим разработчикам: вам нужно добавить ссылку на проект дляSystem.Runtime.Serialization если у вас его еще нет, но он доступен в клиентском профиле .NET 4.0.
Я не знаю, почему я не видел этого раньше, но -1 для использованияnew XmlTextWriter а такжеnew XmlTextReader вместоXmlReader.Create а такжеXmlWriter.Create.
Он не работал с Windows Phone 8 SDK для Windows Phone 7.5 (это Silverlight 3).
@JohnSaunders зачем мне это делать, когда я уже знаю, какой XmlReader или Writer я хочу. Вы смотрели на XmlWriter / Reader.Create? За его вызов существует серьезное наказание, и этот метод должен быть настолько быстрым, насколько это возможно, поскольку он может использоваться в узком цикле, сериализующем множество объектов (я допускаю, что я использовал бы другой метод сериализации, если бы здесь была проблема с производительностью). Но в любом случае рекомендованным способом является использование XmlWriter / Reader.Create, но, поскольку я с самого начала программирую .NET (версия 1), я думаю, что я использовал для некоторых вещей «старый». путь.
@ Adarsha Согласно документации DataContractSerializer, он поддерживает следующие платформы: Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008 (роль сервера не поддерживается), Windows Server 2008 R2 (роль сервера ядра поддерживается с пакетом обновления 1 (SP1) или более поздней версии. ; Itanium не поддерживается) ... В нем не упоминается телефонный SDK ... Windows Phone 7 использует .NET Compact Framework 3.7, поэтому нет DataContractSerializer :-( Я обновил публикацию соответствующим образом, чтобы люди не теряли время на вычисление что не сработало! Спасибо Адарша!
4

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

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

    }
}
В этом коде была ошибка - если в xml были пробелы, чтение могло бы войти в бесконечный цикл. Я исправил эту ошибку, но может быть больше.
4

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

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

13

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

Я бы согласился, что это не в особенно доступном для обнаружения месте.

Я не понимаю комментарий. Почему AREN'T они универсальные xml-сериализуемые словари? Какая часть & quot; System.Xml.XmlDictionary & quot; или & quot; System.Runtime.Serialization & quot; указывает на не универсальность?
Эти классы не являются сериализуемыми словарями общего назначения. Они связаны с реализацией сериализации в WCF.
14

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

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

Веб-сервисы ASMX теперь считаются устаревшей технологией. Увидетьjohnwsaundersiii.spaces.live.com/blog/…, Существует целый API для файлов конфигурации - он не использует сериализацию XML. Что-нибудь еще?
Кстати, «ограничение» это дизайнерское решение. Как вы говорите, он использовался для веб-сервисов - но не только для сериализации и десериализации - именно он породил схемы, являющиеся частью WSDL. Это все часть целого, и все это должно работать вместе.
Вы сказали, что "учитывая, насколько различные функции .NET зависят от сериализации XML" Вы не могли понять, почему не было ни одного. Я сказал, что есть несколько возможностей .NET, зависящих от XML Ser. Вы упомянули ASMX и Config. Я сказал, что ASMX является устаревшим, и в конфигурации не используется XML Ser. & Quot; Наследие & Quot; должен был показать, почему они не спешат добавлять поддержку словаря. Также смjohnwsaundersiii.spaces.live.com/blog/….
Мои два случая - это веб-сервисы и файлы конфигурации. Итак, вы говорите, что ребята из .NET Framework были ограничены недостатком спецификации XML-схемы? Я нашел что-то в Интернете, но использование встроенного класса заняло гораздо меньше времени, чем принятие решения, правильно ли сделал кто-то другой. Я посмотрю на тот, который вы предложили. serialhobbyist
Я знаю, что они унаследованы, но это не значит, что мне дадут время на изучение WCF. Кто-то заметил, что программное обеспечение не должно быть позолочено, оно должно выполнять свою работу. ASMX делает работу. Темпы разработки Microsoft .NET впечатляющие и замечательные, но они не связаны с текущим рынком: бюджеты на обучение сокращаются, сокращаются, выполняются только те работы, которые ДОЛЖНЫ выполняться. Не связанные с ИТ части бизнеса выглядят неуверенно, когда мы говорим: «Нам нужно перейти на новую версию, потому что Microsoft больше не будет поддерживать технологию X». (Я знаю, что это не просто MS, но это часто MS). Так что я застрял с ASMX на данный момент. serialhobbyist
1

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

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
    }

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

Похожие вопросы