2011-09-20 2 views
7

Ho un'applicazione C# .NET 3.5 in cui desidero serializzare una classe contenente uno List<> in XML. La mia classe si presenta così:Serializzare un elenco <> esportato come ICollection <> in XML

[XmlRoot("Foo")] 
class Foo 
{ 
    private List<Bar> bar_ = new List<Bar>(); 

    private string something_ = "My String"; 

    [XmlElement("Something")] 
    public string Something { get { return something_; } } 

    [XmlElement("Bar")] 
    public ICollection<Bar> Bars 
    { 
     get { return bar_; } 
    } 
} 

Se io popolo in questo modo:

Bar b1 = new Bar(); 
// populate b1 with interesting data 
Bar b2 = new Bar(); 
// populate b2 with interesting data 

Foo f = new Foo(); 
f.Bars.Add(b1); 
f.Bars.Add(b2); 

E poi serializzare in questo modo:

using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(@"C:\foo.xml")) 
{ 
    System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo)); 
    serializer.Serialize(textWriter, f); 
} 

ottengo un file che assomiglia a questo:

<Foo> 
    <Something>My String</Something> 
</Foo> 

Ma, quello che voglio è X ML che assomiglia a questo:

<Foo> 
    <Something>My String</Something> 
    <Bar> 
     <!-- Data from first Bar --> 
    </Bar> 
    <Bar> 
     <!-- Data from second Bar --> 
    </Bar> 
</Foo> 

Che cosa devo fare per ottenere la List<> a comparire in XML?

+0

Non credo che si possa 'XmlSerialize' un'interfaccia. Perché vuoi serializzare come 'ICollection' comunque? Serializzare come 'Lista ' e restituire al consumatore un 'ICollection ' ... ??? – IAbstract

+0

@IAbstract - Non sono sicuro di aver capito. Intendi contrassegnare la 'lista privata bar_' con il tag' [XmlElement ("Bar")] '? Questo non cambia l'output. Inoltre, la documentazione di 'XmlSerializer' suggerisce che funzioni con entrambe le interfacce' IEnumerable' e 'ICollection'. http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=VS.90%29.aspx – PaulH

+0

Penso che IAbstract ce l'abbia - non è possibile serializzare un'interfaccia. Quindi, invece, dovresti cambiare Foo in modo che Bars sia un elenco, non un ICollection –

risposta

2

Il XmlSerializer richiede che le proprietà serializzabili abbiano un setter. Inoltre, lo XmlSerializer non può serializzare le proprietà dell'interfaccia. Il seguente codice funzionerà:

[XmlElement("Bar")] 
public List<Bar> Bars 
{ 
    get { return bar_; } 
    set { throw new NotSupportedException("This property 'Bars' cannot be set. This property is readonly."); } 
} 

Se non ti piace questa soluzione (l'eccezione è un pò brutto) allora si potrebbe implementare IXmlSerializable e scrivere il proprio serializzazione personalizzata.

Edit: Artur Mustafin è giusto, i membri che implementano IEnumerable o ICollectionnon hanno bisogno di un setter, come è spiegato sul this msdn page:

Il XmlSerializer dà un trattamento speciale per le classi che implementano IEnumerable oppure ICollection. Una classe che implementa IEnumerable deve implementare un metodo pubblico Add che accetta un singolo parametro. Il parametro Add method deve essere dello stesso tipo restituito dalla proprietà Current sul valore restituito da GetEnumerator o da una delle basi di quel tipo. Una classe che implementa ICollection (ad esempio CollectionBase) oltre a IEnumerable deve avere una proprietà indicizzata pubblica Item (indicizzatore in C#) che accetta un numero intero e deve avere una proprietà pubblica di tipo intero Count. Il parametro del metodo Add deve essere dello stesso tipo restituito dalla proprietà Item o una delle basi di quel tipo. Per le classi che implementano ICollection, i valori da serializzare vengono recuperati dalla proprietà indicizzata Item, non chiamando GetEnumerator.

+2

Questa è una risposta errata e brutta, questo è errato perché funziona evento senza setter, vedere il mio post –

+0

XmlSerializer NON richiede che le proprietà serializzabili abbiano un setter –

3

Dando una risposta corretta, non c'è motivo di creare un setter sgradevole alla proprietà pubblica List<T>, per generare un'eccezione.

Questo perché List<> è già implementato ICollection<T> e fornisce il metodo con la firma void Add(T object) utilizzata dal meccanismo di serializzazione;

Siete solo necessario aggiungere il setter per le proprietà pubbliche di essere serializzato, e cambiare ICollection<T>-List<T>:

[XmlRoot("Foo")] 
public class Foo 
{ 
    private List<Bar> bar_ = new List<Bar>(); 

    [XmlElement("Something")] 
    public string Something { get; set; } 

    [XmlElement("Bar")] 
    public List<Bar> Bars { get { return bar_; } } 
} 

Si otterrà un output:

<?xml version="1.0" encoding="utf-8"?> 
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <Something>My String</Something> 
    <Bar /> 
    <Bar /> 
</Foo> 

Inoltre, si tratta di un migliore idea per serializzare xml in memoria, per vedere i risultati, o testarlo, come segue:

static void Main(string[] args) 
{ 
    Bar b1 = new Bar(); 
    // populate b1 with interesting data 
    Bar b2 = new Bar(); 
    // populate b2 with interesting data 

    Foo f = new Foo(); 
    f.Bars.Add(b1); 
    f.Bars.Add(b2); 
    f.Something = "My String"; 

    using (MemoryStream ms = new MemoryStream()) 
    using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(ms)) 
    { 
     System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo)); 
     serializer.Serialize(textWriter, f); 
     string text = Encoding.UTF8.GetString(ms.ToArray()); 
     Console.WriteLine(text); 
    } 

    Console.ReadKey(false); 
} 

Per serializzare utilizzando le interfacce, utilizzare il mio progetto XmlSerialization