2009-07-14 6 views
72

Ich brauche ein XML-serialisierbares Wörterbuch. Eigentlich habe ich jetzt zwei ganz unterschiedliche Programme, die eins brauchen. Ich war ziemlich überrascht zu sehen, dass .NET keins hat. Ich habe die Frage woanders gestellt und sarkastische Antworten bekommen. Ich verstehe nicht, warum das eine dumme Frage ist.Warum gibt es in .NET kein XML-serialisierbares Wörterbuch?

Kann mir jemand aufklären, wie abhängig verschiedene .NET-Features von der XML-Serialisierung sind, warum es kein XML-serialisierbares Wörterbuch gibt. Hoffentlich kannst du auch erklären, warum manche Leute das für eine dämliche Frage halten. Ich denke, ich muss etwas Grundlegendes vermissen und ich hoffe, dass du die Lücken füllen kannst.

+5

Die Frage ist falsch, weil Ursache und Wirkung falsch sind. Es sollte sein, "warum' XmlSerializer' Wörterbücher nicht serialisieren kann "? Es gibt viele Möglichkeiten, die XML-Serialisierung in .NET durchzuführen, und die meisten von ihnen serialisieren Wörterbücher ('DataContractSerializer',' SoapFormatter' ...). –

+0

Ich vermute, Sie haben nicht "XmlDictionaryWriter.CreateDictionaryWriter" ... oder die 100 anderen Möglichkeiten, Wörterbücher in .NET zu serialisieren (einige von ihnen sind eingebaut). ... Warum brauchst du auch ein Wörterbuch? Ich habe immer stark typisierte Objekte gefunden, die besser funktionieren, warum implementiere ich nicht einfach eine Klasse mit [DataContract] und IExtensibleDataObject? – BrainSlugs83

+0

Welche modernen .NET-Funktionen sind Ihrer Meinung nach von der XML-Serialisierung abhängig? Konfigurationsdateien verwenden keine Serialisierung, und ASMX-Webdienste sind nur für die ältere Verwendung vorgesehen. (bewegt, um aus der Antwort zu kommentieren) –

Antwort

14

Die Sache mit XML-Serialisierung ist, dass es nicht nur darum geht, einen Strom von Bytes zu erstellen. Es geht auch darum, ein XML-Schema zu erstellen, für das dieser Byte-Stream gültig ist. Es gibt keine gute Möglichkeit, im XML-Schema ein Wörterbuch darzustellen. Das Beste, was Sie tun könnten, ist zu zeigen, dass es einen eindeutigen Schlüssel gibt.

Sie können immer Ihren eigenen Wrapper erstellen, zum Beispiel One Way to Serialize Dictionaries.

+0

Meine beiden Fälle sind Web-Services und Konfigurationsdateien. Sie sagen also, dass die .NET Framework-Typen durch einen Mangel in der XML-Schema-Spezifikation eingeschränkt waren? Ich habe Sachen online gefunden, aber mit einer eingebauten Klasse in viel weniger Arbeit als die Entscheidung, ob jemand anderes es richtig gemacht hat. Ich schaue mir den an, den du vorgeschlagen hast. – serialhobbyist

+0

ASMX-Web-Services gelten jetzt als Legacy-Technologie. Siehe http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!860.eintrag. Es gibt eine vollständige API für Konfigurationsdateien - sie verwendet keine XML-Serialisierung. Noch etwas? –

+0

BTW, die "Beschränkung" ist eine Designentscheidung. Wie Sie sagen, wurde es für Web-Services verwendet - aber nicht nur zum Serialisieren und Deserialisieren - es erzeugte die Schemas, die Teil der WSDL sind. Es ist alles Teil eines Ganzen und alles muss zusammenarbeiten. –

4

Erstellen einer eigenen :-), die Nur-Lese-Funktion ist Bonus, aber wenn Sie eine andere Taste als eine Zeichenfolge benötigen dann muss die Klasse einige Änderungen ...

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

    } 
} 
+0

Es gab einen Fehler in diesem Code - wenn Whitespace in der XML-Datei vorhanden war, konnte der Lesevorgang in eine Endlosschleife eintreten. Ich habe diesen Fehler behoben, aber es könnte mehr geben. – Luke

13

Sie fügten hinzu, ein in .NET 3.0. 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 erkennbaren Ort ist.

+4

Diese Klassen sind keine serialisierbaren Wörterbücher für allgemeine Zwecke. Sie beziehen sich auf die Implementierung der Serialisierung in WCF. –

+0

Ich verstehe das Kommment nicht. Warum sind das keine XML-serialisierbaren Wörterbücher? Welcher Teil von "System.Xml.XmlDictionary" oder "System.Runtime.Serialization" weist auf Nicht-Generizität hin? –

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

} 
+4

Sie sollten die Vorteile der Verwendung Ihrer ausführlicheren Lösung gegenüber denen, die an anderer Stelle in diesem Thread vorgeschlagen wurden, kommentieren. Es ist nicht sofort klar, warum man dies über eine einfachere Implementierung verwenden sollte. –

52

Ich weiß, das vor beantwortet wurde, aber da ich eine sehr prägnante Art und Weise (Code) zu tun IDictionary Serialisierung mit der DataContractSerializer Klasse (verwendet von WCF, aber könnte und soll überall dort eingesetzt werden) I couldn‘ t wider es hier einen Beitrag:

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

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

UPDATE: Es nicht Arbeit in .NET Compact Framework (nicht einmal NETCF 3.7 für Windows Phone 7) als DataContractSerializer wird nicht unterstützt!

Ich habe das streaming zu string, weil es für mich bequemer war, obwohl ich könnte eine niedrigere Serialisierung zu Stream eingeführt haben und dann verwendet, um zu Strings serialisieren, aber ich verallgemeinere nur bei Bedarf (genau wie vorzeitige Optimierung ist böse, es ist also eine vorzeitige Verallgemeinerung ...)

Die Nutzung 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 eine wortgetreue Kopie myDict sein wird.

Sie werden auch feststellen, dass die zur Verfügung gestellten generischen Methoden der Lage, jede Art serialisiert werden (nach bestem Wissen und Gewissen), da es nicht auf IDictionary Schnittstellen begrenzt ist, kann es sein, wirklich alle generischen Typ T.

Hoffe es hilft jemandem da draußen!

+4

Funktioniert gut! An andere Entwickler: Sie müssen eine Projektreferenz für 'System.Runtime.Serialization' hinzufügen, wenn Sie noch keine haben, die aber im .NET 4.0-Clientprofil verfügbar ist. – MCattle

+0

Es funktionierte nicht mit Windows Phone 8 SDK, das auf Windows Phone 7.5 (Silverlight 3) abzielte. – Adarsha

+0

@Adarsha Gemäß der DataContractSerializer-Dokumentation werden folgende Plattformen unterstützt: Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008 (Server Core Role wird nicht unterstützt), Windows Server 2008 R2 (Server Core Role wird mit SP1 oder später; Itanium nicht unterstützt) ... Es erwähnt nicht das Telefon-SDK ... Das Windows Phone 7 verwendet .NET Compact Framework 3.7, also keinen DataContractSerializer :-(Ich habe den Beitrag entsprechend aktualisiert, damit die Leute keine Zeit verlieren herauszufinden, was nicht funktioniert! Danke Adarsha! – Loudenvier

1

Ein allgemeiner Helfer schnell IXmlSerializable einen (vorhandener) Wörterbuch hinzugefügt werden, ohne 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 einen günstigen serializable generic 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 
0

Ich weiß, das wurde zu Tod getan jetzt, aber hier ist mein Beitrag. Ich nahm die guten Bits von den Lösungen von @Loudenvier und @Jack und schrieb meine eigene serialisable (sorry, ich bin Britisch) Wörterbuchklasse.

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 nicht explizit müssen serialise oder irgendetwas deserialise, Pumpe nur die ganze Klassenhierarchie durch eine XmlSerializer und du bist fertig.