2009-03-20 16 views
6

generico Il setup:C# che eredita la raccolta e la serializzazione

class Item 
{ 
    private int _value; 

    public Item() 
    { 
     _value = 0; 
    } 

    public int Value { get { return _value; } set { _value = value; } } 
} 

class ItemCollection : Collection<Item> 
{ 
    private string _name; 

    public ItemCollection() 
    { 
     _name = string.Empty; 
    } 

    public string Name { get {return _name;} set {_name = value;} } 
} 

Ora, cercando di serializzare utilizzando il seguente frammento di codice:

ItemCollection items = new ItemCollection(); 

... 

XmlSerializer serializer = new XmlSerializer(typeof(ItemCollection)); 
using (FileStream f = File.Create(fileName)) 
    serializer.Serialize(f, items); 

Upon guardando l'XML risultante vedo che l'ItemCollection.Name il valore non c'è!

Penso che quello che può accadere è che il serializzatore vede il tipo ItemCollection come una semplice raccolta ignorando in tal modo tutte le altre proprietà aggiunti ...

C'è qualcuno dopo aver incontrato un tale problema e trovato una soluzione?

saluti,

Stécy

risposta

12

Questo comportamento è "In base al progetto". Quando deriva da una classe di raccolta, Xml Seralizier serializzerà solo gli elementi della raccolta. Per ovviare a ciò dovresti creare una classe che incapsuli la collezione e il nome e che abbia quella serializzata.

class Wrapper 
{ 
    private Collection<Item> _items; 
    private string _name; 

    public Collection<Item> Items { get {return _items; } set { _items = value; } } 
    public string Name { get { return _name; } set { _name = value; } } 
} 

Una discussione dettagliata è disponibile qui: http://blogs.vertigo.com/personal/chris/Blog/archive/2008/02/01/xml-serializing-a-derived-collection.aspx

+0

+1; si noti inoltre che il comportamento è condiviso anche dalla maggior parte dei framework di associazione dati. Semplicemente non è una buona idea che le collezioni abbiano proprietà; le collezioni hanno articoli (solo) - questo è il loro lavoro. –

+0

Bello, ora ho bisogno di racchiudere diverse classi derivate dalla collezione quindi ... Sono preoccupato che complicherebbe il diagramma di classe anche se ... –

4

XmlSerializer è il male. Detto questo, qualsiasi oggetto che implementa IEnumerable verrà serializzato come una semplice raccolta, ignorando eventuali proprietà aggiuntive che hai aggiunto tu stesso.

Sarà necessario creare una nuova classe che contenga sia la proprietà che una proprietà che restituisce la raccolta.

0

Si può anche provare a implelemnt la propria serializzazione utilizzando IXmlSerializable interfaccia

public class ItemCollection : Collection<Item>,IXmlSerializable 
    { 
     private string _name; 

     public ItemCollection() 
     { 
      _name = string.Empty; 
     } 

     public string Name 
     { 
      get { return _name; } 
      set { _name = value; } 
     } 

#region IXmlSerializable Members 

     public System.Xml.Schema.XmlSchema GetSchema() 
     { 
       return null; 
     } 

     public void ReadXml(System.Xml.XmlReader reader) 
     { 

     } 

     public void WriteXml(System.Xml.XmlWriter writer) 
     { 
       writer.WriteElementString("name", _name); 
       List<Item> coll = new List<Item>(this.Items); 
       XmlSerializer serializer = new XmlSerializer(coll.GetType()); 
       serializer.Serialize(writer, coll); 

     } 

#endregion 
    } 

Sopra codice genererà il XML serializzato come

<?xml version="1.0"?> 
<ItemCollection> 
    <name /> 
    <ArrayOfItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <Item> 
     <Value>1</Value> 
    </Item> 
    <Item> 
     <Value>2</Value> 
    </Item> 
    </ArrayOfItem> 
</ItemCollection> 
+0

Questa sembra essere una buona soluzione. Non hai specificato il codice per il metodo ReadXml e mi chiedo se questo funzionerebbe così com'è. La mia ipotesi è che non sarebbe? –

+0

Se vuoi deserializzare da xml in qualche momento, devi scrivere il ReadXML in altro modo, puoi ignorarlo. Questo codice funziona per la serializzazione. –

2

Non sono sicuro se mi manca qualcosa ng, ma si desidera che il codice XML risultante di essere

<ItemCollection> 
    <Name>name val</Name> 
    <Item> 
     <Value>1</alue> 
    </Item 
    <Item> 
     <Value>2</alue> 
    </Item 
</ItemCollection> 

Se è così, basta applicare l'attributo XmlRoot alla classe ItemCollection e impostare il nome dell'elemento ...

[XmlRoot(ElementName="ItemCollection")] 
public class ItemCollection : Collection<Item> 
{ 
    [XmlElement(ElementName="Name")] 
    public string Name {get;set;} 
} 

Questo indicherà il serializzatore per generare il nome richiesto per il contenitore della raccolta.

0
public class Animals : List<Animal>, IXmlSerializable 
{ 
    private static Type[] _animalTypes;//for IXmlSerializable 
    public Animals() 
    { 
     _animalTypes = GetAnimalTypes().ToArray();//for IXmlSerializable 
    } 

    // this static make you access to the same Animals instance in any other class. 
    private static Animals _animals = new Animals(); 
    public static Animals animals 
    { 
     get {return _animals; } 
     set { _animals = value; } 
    } 

    #region IXmlSerializable Members 

    public System.Xml.Schema.XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(System.Xml.XmlReader reader) 
    { 
     bool wasEmpty = reader.IsEmptyElement; 
     reader.Read(); 
     if (wasEmpty) 
      return; 

     reader.MoveToContent(); 
     reader.ReadStartElement("Animals"); 
     // you MUST deserialize with 'List<Animal>', if Animals class has no 'List<Animal>' fields but has been derived from 'List<Animal>'. 
     List<Animal> coll = GenericSerializer.Deserialize<List<Animal>>(reader, _animalTypes); 
     // And then, You can set 'Animals' to 'List<Animal>'. 
     _animals.AddRange(coll); 
     reader.ReadEndElement(); 

     //Read Closing Element 
     reader.ReadEndElement(); 
    } 

    public void WriteXml(System.Xml.XmlWriter writer) 
    { 
     writer.WriteStartElement("Animals"); 
     // You change 'List<Animal>' to 'Animals' at first. 
     List<Animal> coll = new List<Animal>(_animals); 
     // And then, You can serialize 'Animals' with 'List<Animal>'. 
     GenericSerializer.Serialize<List<Animal>>(coll, writer, _animalTypes); 
     writer.WriteEndElement(); 
    } 

    #endregion 

    public static List<Type> GetAnimalTypes() 
    { 
     List<Type> types = new List<Type>(); 
     Assembly asm = typeof(Animals).Assembly; 
     Type tAnimal = typeof(Animal); 

     //Query our types. We could also load any other assemblies and 
     //query them for any types that inherit from Animal 
     foreach (Type currType in asm.GetTypes()) 
     { 
      if (!currType.IsAbstract 
       && !currType.IsInterface 
       && tAnimal.IsAssignableFrom(currType)) 
       types.Add(currType); 
     } 

     return types; 
    } 
} 
Problemi correlati