XML serializar lista genérica de objetos serializables

¿Puedo serializar una lista genérica de objetos serializables sin tener que especificar su tipo?

Algo así como la intención detrás del código roto a continuación:

List serializableList = new List(); XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType()); serializableList.Add((ISerializable)PersonList); using (StreamWriter streamWriter = System.IO.File.CreateText(fileName)) { xmlSerializer.Serialize(streamWriter, serializableList); } 

Editar:

Para aquellos que querían saber detalles: cuando bash ejecutar este código, se produce un error en la línea […] XMLSerializer con:

No se puede serializar la interfaz System.Runtime.Serialization.ISerializable.

Si cambio a List Obtengo "There was an error generating the XML document." . El detalle de InnerException es "{"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."}"

El objeto persona se define de la siguiente manera:

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

PersonList es solo una List .

Sin embargo, esto solo es para probar, por lo que no creo que los detalles sean demasiado importantes. La clave es que tengo uno o más objetos diferentes, todos los cuales son serializables. Quiero serializarlos todos en un solo archivo. Pensé que la forma más fácil de hacerlo sería ponerlos en una lista genérica y serializar la lista de una vez. Pero esto no funciona.

Intenté con List también, pero eso falla con

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

Perdón por la falta de detalles, pero soy un principiante en esto y no sé qué detalle se requiere. Sería útil que las personas que pidieran más detalles trataran de responder de una manera que me dejara entender qué detalles se requieren, o una respuesta básica que describa las posibles direcciones.

También gracias a las dos respuestas que he obtenido hasta ahora, podría haber pasado mucho más tiempo leyendo sin obtener estas ideas. Es sorprendente lo útil que es la gente en este sitio.

Tengo una solución para una Lista genérica <> con elementos dinámicos enlazados.

clase PersonalList es el elemento raíz

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

clase Persona es un elemento de lista única

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

clase SpecialPerson hereda persona

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

clase SuperPerson hereda persona

 [XmlType("SuperPerson")] // define Type public class SuperPerson : Person { [XmlArray("Skills")] [XmlArrayItem("Skill")] public List Skills { get; set; } [XmlElement("Alias")] public string Alias { get; set; } public SuperPerson() { Skills = new List(); } public SuperPerson(string name, string city, int age, string id, string[] skills, string alias) { Skills = new List(); this.Name = name; this.City = city; this.Age = age; this.ID = id; foreach (string item in skills) { this.Skills.Add(item); } this.Alias = alias; } } 

y la prueba principal Fuente

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

Importante es la definición e incluye los diferentes tipos.

Consulte Introducción a la serialización XML :

Elementos que pueden ser serializados

Los siguientes elementos se pueden serializar utilizando la clase XmlSerializer :

  • Propiedades públicas de lectura / escritura y campos de clases públicas
  • Clases que implementan ICollection o IEnumerable
  • Objetos XmlElement
  • Objetos XmlNode
  • Objetos DataSet

En particular, ISerializable o el atributo [Serializable] no importa.


Ahora que nos ha dicho cuál es su problema (“no funciona” no es un enunciado del problema), puede obtener respuestas a su problema real, en lugar de adivinar.

Cuando serialice una colección de un tipo, pero en realidad estará serializando una colección de instancias de tipos derivados, debe informar al serializador qué tipos va a serializar en realidad. Esto también es cierto para colecciones de object .

Necesita usar el constructor XmlSerializer (Type, Type []) para obtener la lista de tipos posibles.

No puede serializar una colección de objetos sin especificar los tipos esperados. Debe pasar la lista de tipos esperados al constructor de XmlSerializer (el parámetro extraTypes ):

 List list = new List(); 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 todos los objetos de su lista heredan de la misma clase, también puede usar el atributo XmlInclude para especificar los tipos esperados:

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

Creo que es mejor si usa métodos con argumentos generics, como los siguientes:

 public static void SerializeToXml(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(string xml) { T result; var ser = new XmlSerializer(typeof(T)); using (var tr = new StringReader(xml)) { result = (T)ser.Deserialize(tr); } return result; } 

Creo que el enfoque de Dreas está bien. Sin embargo, una alternativa a esto es tener algunos métodos de ayuda estáticos e implementar IXmlSerializable en cada uno de sus métodos, por ejemplo, un método de extensión XmlWriter y el XmlReader uno para volver a leerlo.

 public static void SaveXmlSerialiableElement(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(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 va por la ruta de usar la clase XmlSerializer directamente, cree ensamblajes de serialización de antemano si es posible, ya que puede tomar un gran golpe de rendimiento en la construcción de nuevos XmlSerializers regularmente.

Para una colección necesitas algo como esto:

 public static void SaveXmlSerialiazbleCollection(this XmlWriter writer, String collectionName, String elementName, IEnumerable 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(); } 

A continuación hay una clase Util en mi proyecto:

 namespace Utils { public static class SerializeUtil { public static void SerializeToFormatter(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(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(string path, object obj) { XmlSerializer xs = new XmlSerializer(typeof(T)); using (FileStream fs = File.Create(path)) { xs.Serialize(fs, obj); } } public static T DeserializeFromXML(string path) { XmlSerializer xs = new XmlSerializer(typeof(T)); using (FileStream fs = File.OpenRead(path)) { return (T)xs.Deserialize(fs); } } public static T DeserializeFromXml(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); } } } } 

La forma más fácil de hacerlo, que he encontrado. Aplique el atributo System.Xml.Serialization.XmlArray .

 [System.Xml.Serialization.XmlArray] //This is the part that makes it work List serializableList = new List(); XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType()); serializableList.Add(PersonList); using (StreamWriter streamWriter = System.IO.File.CreateText(fileName)) { xmlSerializer.Serialize(streamWriter, serializableList); } 

El serializador detectará que se trata de una matriz y serializará los elementos de la lista como nodos secundarios.

El parámetro knowTypeList permite serializar con DataContractSerializer varios tipos conocidos:

 private static void WriteObject( string fileName, IEnumerable reflectedInstances, List 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); } } } 

Si se puede cambiar el requisito de salida XML, siempre se puede usar la serialización binaria, que es más adecuada para trabajar con listas de objetos heterogéneas. Aquí hay un ejemplo:

 private void SerializeList(List 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) //... } } 

Use como tal:

 [Serializable] public class Animal { public string Home { get; set; } } [Serializable] public class Person { public string Name { get; set; } } public void ExampleUsage() { List SerializeMeBaby = new List { new Animal { Home = "London, UK" }, new Person { Name = "Skittles" } }; string TargetPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Test1.dat"); SerializeList(SerializeMeBaby, TargetPath); }