2009-07-14 10 views
72

Ho bisogno di un dizionario serializzabile XML. In realtà, ora ho due programmi abbastanza diversi che ne richiedono uno. Sono rimasto piuttosto sorpreso nel constatare che .NET non ne ha uno. Ho fatto la domanda altrove e ho avuto risposte sarcastiche. Non capisco perché sia ​​una domanda stupida.Perché non esiste un dizionario serializzabile XML in .NET?

Qualcuno mi può illuminare, data la dipendenza delle varie funzionalità .NET sulla serializzazione XML, perché non esiste un dizionario serializzabile XML. Spero che tu possa anche spiegare perché alcune persone considerano una domanda stupida. Credo che mi manchi qualcosa di fondamentale e spero che sarai in grado di colmare le lacune.

+5

La domanda è errata, perché causa causa ed effetto errati. Dovrebbe essere, "perché' XmlSerializer' non può serializzare i dizionari "? Perché ci sono molti modi per fare serializzazione XML in .NET, e molti di loro serializzano i dizionari bene ('DataContractSerializer',' SoapFormatter' ...). –

+0

Suppongo che tu non abbia esaminato "XmlDictionaryWriter.CreateDictionaryWriter" ... o gli altri 100 modi per serializzare i dizionari in .NET (alcuni di essi sono integrati). ... anche perché hai bisogno di un dizionario? Ho sempre trovato che gli oggetti dattiloscritti funzionavano meglio, perché non implementare solo una classe con [DataContract] e IExtensibleDataObject? – BrainSlugs83

+0

Quali funzionalità moderne in .NET pensi dipenda dalla serializzazione XML? I file di configurazione non utilizzano la serializzazione e i servizi Web ASMX sono pensati solo per l'utilizzo legacy. (spostato sul commento dalla risposta) –

risposta

14

La cosa che riguarda la serializzazione XML è che non si tratta solo di creare un flusso di byte. Riguarda anche la creazione di uno schema XML a cui questo flusso di byte verrebbe validato. Non esiste un modo valido in XML Schema per rappresentare un dizionario. Il meglio che puoi fare è mostrare che esiste una chiave unica.

È sempre possibile creare il proprio wrapper, ad esempio One Way to Serialize Dictionaries.

+0

I miei due casi sono servizi Web e file di configurazione. Quindi, stai dicendo che i ragazzi di .NET Framework erano limitati da una deficienza nella specifica dello schema XML? Ho trovato roba online ma uso una classe integrata in molto meno lavoro che decidere se qualcun altro ha fatto bene. Dò un'occhiata a quello che hai suggerito. – serialhobbyist

+0

I servizi Web ASMX sono ora considerati tecnologia legacy. Vedi http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!860.entry. C'è un'intera API per i file di configurazione - non utilizza la serializzazione XML. Qualunque altra cosa? –

+0

BTW, la "limitazione" è una decisione progettuale. Come dici tu, è stato utilizzato per i servizi Web, ma non solo per serializzare e deserializzare: è ciò che ha prodotto gli schemi che fanno parte del WSDL. Fa tutto parte di un tutto e tutto deve funzionare insieme. –

4

Crea uno proprio :-), la funzionalità di sola lettura è bonus, ma se avete bisogno di un tasto diverso da una stringa allora la classe ha bisogno di alcune modifiche ...

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

C'era un bug in questo codice - se ci fossero spazi bianchi nel xml la lettura poteva entrare in un ciclo infinito. Ho corretto questo errore ma potrebbero essercene di più. – Luke

13

hanno aggiunto uno in .NET 3.0. Se è possibile, aggiungere un riferimento a System.Runtime.Serialization e cercare System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader e System.Xml.XmlDictionaryWriter.

Concordo sul fatto che non si trovi in ​​un luogo particolarmente individuabile.

+4

Queste classi non sono dizionari serializzabili generici. Sono correlati all'implementazione della serializzazione in WCF. –

+0

Non capisco l'impegno. Perché NON SONO quei dizionari xml-serializzabili generici? Quale parte di "System.Xml.XmlDictionary" o "System.Runtime.Serialization" indica non genericità? –

4

Utilizzare DataContractSerializer! Guarda l'esempio qui sotto.

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

Il codice precedente produce il seguente codice 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

Questa è la mia implementazione.

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

Dovresti commentare i vantaggi dell'uso della tua soluzione più prolissa rispetto a quelli proposti altrove in questo thread. Non è immediatamente chiaro perché si dovrebbe usare questo su un'implementazione più semplice. –

52

So che questo è stato risposto prima, ma dal momento che ho un modo molto conciso (codice) per fare IDictionary serializzazione con la classe DataContractSerializer (usato da WCF, ma potrebbe e dovrebbe essere utilizzato ovunque) non potevo' t resistono contribuendo qui:

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

Questo funziona perfettamente in .NET 4 e dovrebbe funzionare anche in .NET 3.5, anche se non ho la prova ancora.

UPDATE: E non lavorare in .NET Compact Framework (nemmeno NETCF 3.7 per Windows Phone 7) come il DataContractSerializer non è supportato!

Ho eseguito lo streaming su string perché era più conveniente per me, anche se avrei potuto introdurre una serializzazione di livello inferiore su Stream e quindi usarlo per serializzare su stringhe, ma tendo a generalizzare solo quando necessario (proprio come ottimizzazione prematura è il male, quindi è prematuro generalizzazione ...)

uso è molto semplice:

// 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 sarà una copia letterale di myDict.

Noterete anche che i metodi generici forniti saranno in grado di serializzare qualsiasi tipo (al meglio delle mie conoscenze), in quanto non si limita alle interfacce IDictionary, può essere davvero qualsiasi tipo T. generico

Spero che aiuti qualcuno là fuori!

+4

Funziona alla grande! Agli altri sviluppatori: dovrai aggiungere un riferimento al progetto per 'System.Runtime.Serialization' se non ne hai già uno, ma è disponibile nel profilo del client .NET 4.0. – MCattle

+0

Non ha funzionato con Windows Phone 8 SDK che utilizza Windows Phone 7.5 (che è Silverlight 3). – Adarsha

+0

@Adarsha Per la documentazione DataContractSerializer supporta le seguenti piattaforme: Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008 (ruolo Server Core non supportato), Windows Server 2008 R2 (ruolo Server Core supportato con SP1 o dopo, Itanium non supportato) ... Non menziona l'SDK del telefono ... Windows Phone 7 utilizza .NET Compact Framework 3.7, quindi nessun DataContractSerializer :-(Ho aggiornato il post di conseguenza, così le persone non perdono tempo Per capire cosa non ha funzionato! Grazie Adarsha! – Loudenvier

1

Un aiuto generico per aggiungere rapidamente IXmlSerializable a qualsiasi dizionario (esistente) senza usare l'ereditarietà:

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 

E una vantaggiosa serializzabile dizionario generico:

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

So che questo è stato fatto per la morte ora, ma qui è il mio contributo. Ho preso le cose buone dalle soluzioni di @Loudenvier e @Jack e ho scritto la mia classe di dizionario serializzabile (mi dispiace, sono inglese).

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

Mi piace questo approccio, perché non sarà necessario a puntate in modo esplicito o deserialise nulla, solo pompare l'intera gerarchia delle classi attraverso un XmlSerializer e il gioco è fatto.