Warum gibt es in .NET kein XML-serialisierbares Wörterbuch?

73

Ich benötige ein XML-serialisierbares Wörterbuch. Eigentlich habe ich jetzt zwei ganz unterschiedliche Programme, die eines brauchen. Ich war ziemlich überrascht zu sehen, dass .NET keine hat. Ich stellte die Frage woanders und bekam sarkastische Antworten. Ich verstehe nicht, warum es eine dumme Frage ist.

Kann mich jemand aufklären, warum es kein XML-serialisierbares Wörterbuch gibt, wenn man bedenkt, wie abhängig verschiedene .NET-Funktionen von der XML-Serialisierung sind? Hoffentlich können Sie auch erklären, warum manche Leute das für eine blöde Frage halten. Ich denke, mir muss etwas Grundlegendes fehlen, und ich hoffe, dass Sie die Lücken füllen können.

Serialhobbyist
quelle
5
Die Frage ist falsch, weil Ursache und Wirkung falsch sind. Es sollte lauten: "Warum XmlSerializerkönnen Wörterbücher nicht serialisiert werden?" Weil es in .NET viele Möglichkeiten gibt, XML-Serialisierung durchzuführen, und die meisten von ihnen Wörterbücher problemlos serialisieren ( DataContractSerializer, SoapFormatter...).
Pavel Minaev
Ich vermute, Sie haben "XmlDictionaryWriter.CreateDictionaryWriter" oder die 100 anderen Möglichkeiten zum Serialisieren von Wörterbüchern in .NET nicht untersucht (einige davon sind integriert). ... Warum brauchst du auch ein Wörterbuch? Ich habe immer stark typisierte Objekte gefunden, die besser funktionieren. Warum nicht einfach eine Klasse mit einem [DataContract] und IExtensibleDataObject implementieren?
BrainSlugs83
Welche modernen Funktionen in .NET hängen Ihrer Meinung nach von der XML-Serialisierung ab? Konfigurationsdateien verwenden keine Serialisierung, und ASMX-Webdienste sind nur für ältere Zwecke gedacht. (bewegt, um von der Antwort zu kommentieren)
John Saunders

Antworten:

14

Bei der XML-Serialisierung geht es nicht nur darum, einen Bytestrom zu erstellen. Es geht auch darum, ein XML-Schema zu erstellen, anhand dessen dieser Bytestrom validiert wird. Im XML-Schema gibt es keine gute Möglichkeit, ein Wörterbuch darzustellen. Das Beste, was Sie tun können, ist zu zeigen, dass es einen eindeutigen Schlüssel gibt.

Sie können jederzeit Ihren eigenen Wrapper erstellen, z. B. One Way to Serialize Dictionaries .

John Saunders
quelle
Meine beiden Fälle sind Webdienste und Konfigurationsdateien. Sie sagen also, dass die .NET Framework-Mitarbeiter durch einen Mangel in der XML-Schema-Spezifikation eingeschränkt wurden? Ich habe Dinge online gefunden, aber die Verwendung einer integrierten Klasse in viel weniger Arbeit als die Entscheidung, ob jemand anderes es richtig gemacht hat. Ich werde mir die von Ihnen vorgeschlagene ansehen.
Serialhobbyist
ASMX-Webdienste gelten heute als Legacy-Technologie. Siehe johnwsaundersiii.spaces.live.com/blog/… . Es gibt eine komplette API für Konfigurationsdateien - sie verwendet keine XML-Serialisierung. Noch etwas?
John Saunders
Übrigens ist die "Einschränkung" eine Entwurfsentscheidung. Wie Sie sagen, wurde es für Webdienste verwendet - aber nicht nur zum Serialisieren und Deserialisieren - es hat die Schemas erzeugt, die Teil der WSDL sind. Es ist alles Teil eines Ganzen und alles muss zusammenarbeiten.
John Saunders
Ich weiß, dass sie Vermächtnis sind, aber das bedeutet nicht, dass ich die Zeit bekommen werde, WCF zu lernen. Jemand bemerkte, dass Software nicht vergoldet werden sollte, sondern die Arbeit erledigen sollte. ASMX erledigt den Job. Das Tempo bei der Entwicklung von .NET durch Microsoft ist aufregend und wunderbar, aber nicht mit dem aktuellen Markt verbunden: Schulungsbudgets gekürzt, gekürzt, nur Arbeiten ausgeführt, die erledigt werden MÜSSEN. Die Nicht-IT-Bereiche des Geschäfts sehen schief aus, wenn wir sagen: "Wir müssen ein Upgrade durchführen, weil Microsoft Technologie X nicht mehr unterstützt." (Ich weiß, dass es nicht nur MS ist, sondern OFTEN MS.) Also bin ich vorerst bei ASMX geblieben.
Serialhobbyist
Sie sagten, "angesichts der Abhängigkeit verschiedener .NET-Funktionen von der XML-Serialisierung" könnten Sie nicht verstehen, warum es keine gab. Ich sagte, es gibt nur wenige Funktionen von .NET, die von XML Ser abhängen. Sie haben ASMX und Config erwähnt. Ich sagte, ASMX ist Legacy und die Konfiguration verwendet kein XML Ser. "Legacy" sollte zeigen, warum sie es nicht eilig haben würden, Wörterbuchunterstützung hinzuzufügen. Siehe auch johnwsaundersiii.spaces.live.com/blog/… .
John Saunders
52

Ich weiß, dass dies bereits beantwortet wurde, aber da ich eine sehr präzise Methode (Code) für die IDictionary-Serialisierung mit der DataContractSerializer-Klasse habe (von WCF verwendet, aber überall verwendet werden könnte und sollte), konnte ich nicht widerstehen, sie hier beizutragen:

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

Dies funktioniert perfekt in .NET 4 und sollte auch in .NET 3.5 funktionieren, obwohl ich es noch nicht getestet habe.

UPDATE: Es funktioniert nicht in .NET Compact Framework (nicht einmal NETCF 3.7 für Windows Phone 7), da das DataContractSerializernicht unterstützt wird!

Ich habe das Streaming zu String durchgeführt, weil es für mich bequemer war, obwohl ich Stream auf niedrigerer Ebene hätte serialisieren und dann zum Serialisieren in Strings verwenden können, aber ich neige dazu, nur bei Bedarf zu verallgemeinern (genau wie vorzeitige Optimierung böse ist , also ist es vorzeitige Verallgemeinerung ...)

Die Verwendung ist sehr einfach:

// 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 ist eine wörtliche Kopie von myDict.

Sie werden auch feststellen, dass die bereitgestellten generischen Methoden in der Lage sind, jeden Typ (nach meinem besten Wissen) zu serialisieren, da er nicht auf IDictionary-Schnittstellen beschränkt ist, sondern wirklich jeder generische Typ T sein kann.

Hoffe es hilft jemandem da draußen!

Loudenvier
quelle
5
Funktioniert super! An andere Entwickler: Sie müssen eine Projektreferenz hinzufügen, System.Runtime.Serializationwenn Sie noch keine haben, diese jedoch im .NET 4.0-Clientprofil verfügbar ist.
MCattle
Es funktionierte nicht mit Windows Phone 8 SDK für Windows Phone 7.5 (Silverlight 3).
Adarsha
@Adarsha Gemäß der DataContractSerializer-Dokumentation werden die folgenden Plattformen unterstützt: Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008 (Serverkernrolle nicht unterstützt), Windows Server 2008 R2 (Serverkernrolle mit SP1 oder höher unterstützt) ; Itanium wird nicht unterstützt) ... Das Telefon-SDK wird nicht erwähnt ... Windows Phone 7 verwendet .NET Compact Framework 3.7, daher kein DataContractSerializer :-( Ich habe den Beitrag entsprechend aktualisiert, damit die Leute keine Zeit verlieren heraus, was nicht funktioniert hat! Danke Adarsha!
Loudenvier
Ich weiß nicht, warum ich das vorher nicht gesehen habe, aber -1 für die Verwendung von new XmlTextWriterund new XmlTextReaderanstelle von XmlReader.Createund XmlWriter.Create.
John Saunders
1
@ JohnSaunders warum sollte ich das tun, wenn ich bereits weiß, welchen XmlReader oder Writer ich möchte. Haben Sie sich XmlWriter / Reader.Create angesehen? Das Aufrufen ist mit einer erheblichen Strafe verbunden, und diese Methode sollte so schnell wie möglich sein, da sie in einer engen Schleife verwendet werden kann, in der viele Objekte serialisiert werden (ich gebe zu, dass ich eine andere Serialisierungsmethode verwenden würde, wenn hier die Leistung das Problem wäre). Die empfohlene Methode ist jedoch die Verwendung von XmlWriter / Reader.Create. Da ich jedoch .NET von Anfang an programmiere (Version 1), bin ich wahrscheinlich daran gewöhnt, einige Dinge auf die "alte" Weise auszuführen.
Loudenvier
13

Sie haben eine in .NET 3.0 hinzugefügt. Wenn Sie können, fügen Sie einen Verweis auf System.Runtime.Serialization hinzu und suchen Sie nach System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader und System.Xml.XmlDictionaryWriter.

Ich würde zustimmen, dass es nicht an einem besonders auffindbaren Ort ist.

Joe Chung
quelle
4
Diese Klassen sind keine universellen serialisierbaren Wörterbücher. Sie beziehen sich auf die Implementierung der Serialisierung in WCF.
John Saunders
Ich verstehe das Commment nicht. Warum sind sie keine universellen XML-serialisierbaren Wörterbücher? Welcher Teil von "System.Xml.XmlDictionary" oder "System.Runtime.Serialization" weist auf Nicht-Generizität hin?
Robin Davies
4

Verwenden Sie den DataContractSerializer! Siehe das Beispiel unten.

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

Der obige Code erzeugt die folgende 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>
John Saunders
quelle
4

Erstellen Sie eine eigene :-), die schreibgeschützte Funktion ist ein Bonus, aber wenn Sie einen anderen Schlüssel als eine Zeichenfolge benötigen, muss die Klasse einige Änderungen vornehmen ...

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

    }
}
Knall
quelle
Es gab einen Fehler in diesem Code - wenn die XML-Datei Leerzeichen enthielt, konnte der Messwert in eine Endlosschleife eintreten. Ich habe diesen Fehler behoben, aber es kann noch mehr geben.
Luke
2

Ein allgemeiner Helfer zum schnellen Hinzufügen von IXmlSerializable zu einem (vorhandenen) Wörterbuch ohne Verwendung der Vererbung:

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

Und ein praktisches serialisierbares generisches Wörterbuch:

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
Jack
quelle
1

Dies ist meine Implementierung.

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
    }

}
Tümay Tuzcu
quelle
4
Sie sollten die Vorteile der Verwendung Ihrer ausführlicheren Lösung gegenüber denen kommentieren, die an anderer Stelle in diesem Thread vorgeschlagen wurden. Es ist nicht sofort klar, warum man dies für eine einfachere Implementierung verwenden sollte.
Renaud Bompuis
1

Ich weiß, dass dies jetzt zu Tode getan wurde, aber hier ist mein Beitrag. Ich habe die guten Teile aus den Lösungen von @Loudenvier und @Jack genommen und meine eigene serialisierbare (sorry, ich bin Brite) Wörterbuchklasse geschrieben.

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

Ich mag diesen Ansatz, weil Sie nichts explizit serialisieren oder deserialisieren müssen. Pumpen Sie einfach die gesamte Klassenhierarchie durch einen XmlSerializer und Sie sind fertig.

Steztric
quelle