web-dev-qa-db-fra.com

XML Sérialiser une liste générique d'objets sérialisables

Puis-je sérialiser une liste générique d'objets sérialisables sans avoir à spécifier leur type?.

Quelque chose comme l'intention derrière le code cassé ci-dessous:

List<ISerializable> serializableList = new List<ISerializable>();

XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());

serializableList.Add((ISerializable)PersonList);

using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xmlSerializer.Serialize(streamWriter, serializableList);
}

Modifier:

Pour ceux qui voulaient connaître les détails: lorsque j'essaie d'exécuter ce code, il affiche une erreur sur la ligne [...] XMLSerializer avec:

Impossible de sérialiser l'interface System.Runtime.Serialization.ISerializable.

Si je change en List<object> je reçois "There was an error generating the XML document.". Le détail InnerException est "{"The type System.Collections.Generic.List1[[Project1.Person, ConsoleFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] may not be used in this context."}"

L'objet personne est défini comme suit:

[XmlRoot("Person")]
public class Person
{
    string _firstName = String.Empty;
    string _lastName = String.Empty;

    private Person()
    {
    }

    public Person(string lastName, string firstName)
    {
        _lastName = lastName;
        _firstName = firstName;
    }

    [XmlAttribute(DataType = "string", AttributeName = "LastName")]
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    [XmlAttribute(DataType = "string", AttributeName = "FirstName")]
    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }
}

La PersonList est juste un List<Person>.

Ceci est juste pour les tests, donc je ne pensais pas que les détails étaient trop importants. La clé est que j'ai un ou plusieurs objets différents, tous sérialisables. Je veux tous les sérialiser dans un fichier. Je pensais que le moyen le plus simple de le faire serait de les mettre dans une liste générique et de les sérialiser en une fois. Mais ça ne marche pas.

J'ai aussi essayé avec List<IXmlSerializable>, mais ça échoue avec

System.Xml.Serialization.IXmlSerializable cannot be serialized because it does not have a parameterless constructor.

Désolé pour le manque de détails, mais je suis un débutant et je ne sais pas quels détails sont requis. Il serait utile que les personnes demandant plus de détails essaient de répondre de manière à me laisser comprendre quels détails sont nécessaires ou à obtenir une réponse de base indiquant les orientations possibles.

Aussi merci aux deux réponses que j'ai obtenues jusqu'à présent - j'aurais pu passer plus de temps à lire sans avoir ces idées. C'est incroyable à quel point les gens sont utiles sur ce site.

68
Simon D

J'ai une solution pour une liste générique <> avec des éléments liés dynamiques.

classe PersonalList c'est l'élément racine

[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))] // include type class Person
public class PersonalList
{
    [XmlArray("PersonenArray")]
    [XmlArrayItem("PersonObjekt")]
    public List<Person> Persons = new List<Person>();

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

    // Konstruktoren 
    public PersonalList() { }

    public PersonalList(string name)
    {
        this.Listname = name;
    }

    public void AddPerson(Person person)
    {
        Persons.Add(person);
    }
}

classe personne c'est un élément de liste unique 

[XmlType("Person")] // define Type
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]  
        // include type class SpecialPerson and class SuperPerson
public class Person
{
    [XmlAttribute("PersID", DataType = "string")]
    public string ID { get; set; }

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

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

    [XmlElement("Age")]
    public int Age { get; set; }

    // Konstruktoren 
    public Person() { }

    public Person(string name, string city, int age, string id)
    {
        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
    }
}

la classe SpecialPerson hérite de la personne

[XmlType("SpecialPerson")] // define Type
public class SpecialPerson : Person
{
    [XmlElement("SpecialInterests")]
    public string Interests { get; set; }

    public SpecialPerson() { }

    public SpecialPerson(string name, string city, int age, string id, string interests)
    {
        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
        this.Interests = interests;
    }
}

classe SuperPerson hérite de la personne

[XmlType("SuperPerson")] // define Type
public class SuperPerson : Person
{
    [XmlArray("Skills")]
    [XmlArrayItem("Skill")]
    public List<String> Skills { get; set; }

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

    public SuperPerson() 
    {
        Skills = new List<String>();
    }

    public SuperPerson(string name, string city, int age, string id, string[] skills, string alias)
    {
        Skills = new List<String>();

        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
        foreach (string item in skills)
        {
            this.Skills.Add(item);   
        }
        this.Alias = alias;
    }
}

et la source de test principale

static void Main(string[] args)
{
    PersonalList personen = new PersonalList(); 
    personen.Listname = "Friends";

    // normal person
    Person normPerson = new Person();
    normPerson.ID = "0";
    normPerson.Name = "Max Man";
    normPerson.City = "Capitol City";
    normPerson.Age = 33;

    // special person
    SpecialPerson specPerson = new SpecialPerson();
    specPerson.ID = "1";
    specPerson.Name = "Albert Einstein";
    specPerson.City = "Ulm";
    specPerson.Age = 36;
    specPerson.Interests = "Physics";

    // super person
    SuperPerson supPerson = new SuperPerson();
    supPerson.ID = "2";
    supPerson.Name = "Superman";
    supPerson.Alias = "Clark Kent";
    supPerson.City = "Metropolis";
    supPerson.Age = int.MaxValue;
    supPerson.Skills.Add("fly");
    supPerson.Skills.Add("strong");

    // Add Persons
    personen.AddPerson(normPerson);
    personen.AddPerson(specPerson);
    personen.AddPerson(supPerson);

    // Serialize 
    Type[] personTypes = { typeof(Person), typeof(SpecialPerson), typeof(SuperPerson) };
    XmlSerializer serializer = new XmlSerializer(typeof(PersonalList), personTypes); 
    FileStream fs = new FileStream("Personenliste.xml", FileMode.Create); 
    serializer.Serialize(fs, personen); 
    fs.Close(); 
    personen = null;

    // Deserialize 
    fs = new FileStream("Personenliste.xml", FileMode.Open); 
    personen = (PersonalList)serializer.Deserialize(fs); 
    serializer.Serialize(Console.Out, personen);
    Console.ReadLine();
}

L'important est la définition et l'inclusion des différents types.

70
Damasch

Voir Introduction à la sérialisation XML :

Articles pouvant être sérialisés

Les éléments suivants peuvent être sérialisés à l'aide de XmlSerializer classe:

  • Propriétés de lecture/écriture publiques et champs de classes publiques
  • Classes implémentant (ICollection ou IEnumerable
  • _ {XmlElement objets
  • _ {XmlNode objets
  • _ {DataSet objets

En particulier, ISerializable ou l'attribut [Serializable] n'a pas d'importance.


Maintenant que vous nous avez dit quel est votre problème ("ça ne marche pas" n'est pas une affirmation de problème), vous pouvez obtenir des réponses à votre problème actuel au lieu de deviner.

Lorsque vous sérialisez une collection d'un type alors que vous sérialisez une collection d'instances de types dérivés, vous devez indiquer au sérialiseur quels types vous allez sérialiser. Cela est également vrai pour les collections de object.

Vous devez utiliser le constructeur XmlSerializer (Type, Type []) pour donner la liste des types possibles.

21
John Saunders

Vous ne pouvez pas sérialiser une collection d'objets sans spécifier les types attendus. Vous devez transmettre la liste des types attendus au constructeur de XmlSerializer (paramètre extraTypes):

List<object> list = new List<object>();
list.Add(new Foo());
list.Add(new Bar());

XmlSerializer xs = new XmlSerializer(typeof(object), new Type[] {typeof(Foo), typeof(Bar)});
using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xs.Serialize(streamWriter, list);
}

Si tous les objets de votre liste héritent de la même classe, vous pouvez également utiliser l'attribut XmlInclude pour spécifier les types attendus:

[XmlInclude(typeof(Foo)), XmlInclude(typeof(Bar))]
public class MyBaseClass
{
}
5
Thomas Levesque

Je pense que c'est mieux si vous utilisez des méthodes avec des arguments génériques, comme suit:

public static void SerializeToXml<T>(T obj, string fileName)
{
    using (var fileStream = new FileStream(fileName, FileMode.Create))
    { 
        var ser = new XmlSerializer(typeof(T)); 
        ser.Serialize(fileStream, obj);
    }
}

public static T DeserializeFromXml<T>(string xml)
{
    T result;
    var ser = new XmlSerializer(typeof(T));
    using (var tr = new StringReader(xml))
    {
        result = (T)ser.Deserialize(tr);
    }
    return result;
}
4
Andreas Grech

Je pense que l'approche de Dreas est ok. Une alternative à cela consiste toutefois à disposer de méthodes d'assistance statiques et à implémenter IXmlSerializable sur chacune de vos méthodes, par exemple une méthode d'extension XmlWriter et la méthode XmlReader pour le relire.

public static void SaveXmlSerialiableElement<T>(this XmlWriter writer, String elementName, T element) where T : IXmlSerializable
{
   writer.WriteStartElement(elementName);
   writer.WriteAttributeString("TYPE", element.GetType().AssemblyQualifiedName);
   element.WriteXml(writer);
   writer.WriteEndElement();
}

public static T ReadXmlSerializableElement<T>(this XmlReader reader, String elementName) where T : IXmlSerializable
{
   reader.ReadToElement(elementName);

   Type elementType = Type.GetType(reader.GetAttribute("TYPE"));
   T element = (T)Activator.CreateInstance(elementType);
   element.ReadXml(reader);
   return element;
}

Si vous envisagez d'utiliser directement la classe XmlSerializer, créez si possible des assemblys de sérialisation, car vous pouvez générer des performances considérables en construisant régulièrement de nouveaux XmlSerializers.

Pour une collection, vous avez besoin de quelque chose comme ça:

public static void SaveXmlSerialiazbleCollection<T>(this XmlWriter writer, String collectionName, String elementName, IEnumerable<T> items) where T : IXmlSerializable
{
   writer.WriteStartElement(collectionName);
   foreach (T item in items)
   {
      writer.WriteStartElement(elementName);
      writer.WriteAttributeString("TYPE", item.GetType().AssemblyQualifiedName);
      item.WriteXml(writer);
      writer.WriteEndElement();
   }
   writer.WriteEndElement();
}
3
Ian

La façon la plus simple de le faire, que j'ai trouvée .. Appliquez-lui l'attribut System.Xml.Serialization.XmlArray.

[System.Xml.Serialization.XmlArray] //This is the part that makes it work
List<object> serializableList = new List<object>();

XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());

serializableList.Add(PersonList);

using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xmlSerializer.Serialize(streamWriter, serializableList);
}

Le sérialiseur détecte qu'il s'agit d'un tableau et sérialise les éléments de la liste en tant que nœuds enfants.

2
Lee

Ci-dessous, une classe Util dans mon projet:

namespace Utils
{
    public static class SerializeUtil
    {
        public static void SerializeToFormatter<F>(object obj, string path) where F : IFormatter, new()
        {
            if (obj == null)
            {
                throw new NullReferenceException("obj Cannot be Null.");
            }

            if (obj.GetType().IsSerializable == false)
            {
                //  throw new 
            }
            IFormatter f = new F();
            SerializeToFormatter(obj, path, f);
        }

        public static T DeserializeFromFormatter<T, F>(string path) where F : IFormatter, new()
        {
            T t;
            IFormatter f = new F();
            using (FileStream fs = File.OpenRead(path))
            {
                t = (T)f.Deserialize(fs);
            }
            return t;
        }

        public static void SerializeToXML<T>(string path, object obj)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (FileStream fs = File.Create(path))
            {
                xs.Serialize(fs, obj);
            }
        }

        public static T DeserializeFromXML<T>(string path)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (FileStream fs = File.OpenRead(path))
            {
                return (T)xs.Deserialize(fs);
            }
        }

        public static T DeserializeFromXml<T>(string xml)
        {
            T result;

            var ser = new XmlSerializer(typeof(T));
            using (var tr = new StringReader(xml))
            {
                result = (T)ser.Deserialize(tr);
            }
            return result;
        }


        private static void SerializeToFormatter(object obj, string path, IFormatter formatter)
        {
            using (FileStream fs = File.Create(path))
            {
                formatter.Serialize(fs, obj);
            }
        }
    }
}
2
ligaoren

Si l'exigence de sortie XML peut être modifiée, vous pouvez toujours utiliser la sérialisation binaire, ce qui convient mieux à l'utilisation de listes d'objets hétérogènes. Voici un exemple:

private void SerializeList(List<Object> Targets, string TargetPath)
{
    IFormatter Formatter = new BinaryFormatter();

    using (FileStream OutputStream = System.IO.File.Create(TargetPath))
    {
        try
        {
            Formatter.Serialize(OutputStream, Targets);
        } catch (SerializationException ex) {
            //(Likely Failed to Mark Type as Serializable)
            //...
        }
}

Utiliser comme tel: 

[Serializable]
public class Animal
{
    public string Home { get; set; }
}

[Serializable]
public class Person
{
    public string Name { get; set; }
}


public void ExampleUsage() {

    List<Object> SerializeMeBaby = new List<Object> {
        new Animal { Home = "London, UK" },
        new Person { Name = "Skittles" }
    };

    string TargetPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "Test1.dat");

    SerializeList(SerializeMeBaby, TargetPath);
}
0
Robert Venables

Paramètre knowTypeList let série avec DataContractSerializer plusieurs types connus:

private static void WriteObject(
        string fileName, IEnumerable<Vehichle> reflectedInstances, List<Type> knownTypeList)
    {
        using (FileStream writer = new FileStream(fileName, FileMode.Append))
        {
            foreach (var item in reflectedInstances)
            {
                var serializer = new DataContractSerializer(typeof(Vehichle), knownTypeList);
                serializer.WriteObject(writer, item);
            }
        }
    }
0
Sharunas Bielskis