2009-03-20 3 views
6

Das Setup vererben:C# generische Sammlung und Serialisierung

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

nun versuchen, mit dem folgende Codefragment serialisiert:

ItemCollection items = new ItemCollection(); 

... 

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

Nach an dem resultierenden XML-Suche ich sehe, dass die ItemCollection.Name Wert ist nicht da!

Ich denke, was passieren kann, ist, dass der Serializer die ItemCollection Typ als eine einfache Sammlung sieht somit keine andere zusätzliche Eigenschaften zu ignorieren ...

Gibt es jemand ein solches Problem haben festgestellt und eine Lösung gefunden?

Grüße,

Stecy

Antwort

12

Dieses Verhalten ist "Nach Entwurf". Wenn der Xml Seralizier von einer Collection-Klasse abgeleitet wird, werden die Collection-Elemente nur serialisiert. Um dies zu umgehen, sollten Sie eine Klasse erstellen, die die Sammlung und den Namen kapselt und serialisiert.

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

Eine ausführliche Diskussion gibt es hier: http://blogs.vertigo.com/personal/chris/Blog/archive/2008/02/01/xml-serializing-a-derived-collection.aspx

+0

+1; Beachten Sie auch, dass das Verhalten von den meisten Frameworks für Datenbindungen ebenfalls geteilt wird. Es ist einfach keine gute Idee, dass Sammlungen Eigenschaften haben. Sammlungen haben Gegenstände (nur) - das ist ihre Aufgabe. –

+0

Schön, jetzt muss ich mehrere Sammlung-abgeleitete Klassen dann wickeln ... Ich bin besorgt, dass es das Klassendiagramm zwar verkomplizieren würde ... –

4

XmlSerializer ist böse. Das heißt, jedes Objekt, das IEnumerable implementiert, wird als einfache Sammlung serialisiert und ignoriert zusätzliche Eigenschaften, die Sie selbst hinzugefügt haben.

Sie müssen eine neue Klasse erstellen, die sowohl Ihre Eigenschaft als auch eine Eigenschaft enthält, die die Auflistung zurückgibt.

0

Sie auch Ihre eigene Serialisierung mit IXmlSerializable Schnittstelle

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 
    } 

Above Code versuchen können implelemnt das serialisierte XML als

<?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> 
erzeugen
+0

Das scheint eine gute Lösung zu sein. Sie haben keinen Code für die ReadXml-Methode angegeben und ich frage mich, ob das so funktionieren würde. Meine Vermutung ist, dass es nicht wäre? –

+0

Wenn Sie von XML irgendwann deserialisieren wollen, dann müssen Sie die ReadXML anders schreiben, Sie können das ignorieren. Dieser Code funktioniert für die Serialisierung. –

2

Ich bin mir nicht sicher, ob mir etwas fehlt ng, aber wollen Sie das resultierende xml

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

Wenn ja sein, nur das XmlRoot Attribut auf die ItemCollection Klasse gilt und die Elementnamen gesetzt ...

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

Dies wird die Serializer anweisen um den erforderlichen Namen für Ihren Sammelbehälter auszugeben.

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