2010-09-13 7 views
12

Estoy tratando de usar el XmlSerializer para mantener una Lista (T) donde T es una interfaz. El serializador no le gusta las interfaces. Tengo curiosidad por saber si existe una forma sencilla de serializar fácilmente una lista de objetos heterogéneos con XmlSerializer. Esto es lo que estoy buscando:XmlSerializer serialize generic Lista de interfaz

public interface IAnimal 
    { 
     int Age(); 
    } 
    public class Dog : IAnimal 
    { 
     public int Age() 
     { 
      return 1; 
     } 
    } 
    public class Cat : IAnimal 
    { 
     public int Age() 
     { 
      return 1; 
     } 
    } 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     var animals = new List<IAnimal> 
     { 
      new Dog(), 
      new Cat() 
     }; 

     var x = new XmlSerializer(animals.GetType()); 
     var b = new StringBuilder(); 
     var w = XmlTextWriter.Create(b, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true }); 
     //FAIL - cannot serialize interface. Does easy way to do this exist? 
     x.Serialize(w, animals); 
     var s = b.ToString();  
    } 
+0

Tal vez este tema de ayuda que http: // stackoverflow.com/questions/10225174/using-datacontractserializer-and-dataprotectionprovider-to-serialize-and-encryp – saramgsilva

Respuesta

3

¿Tienes que usar XmlSerializer? Este es un problema conocido con XmlSerializer.

Puede utilizar BinaryFormatter para salvar a una corriente:

BinaryFormatter bf = new BinaryFormatter(); 
MemoryStream ms = new MemoryStream(); 
bf.Serialize(ms, animals); 

Otra alternativa es utilizar WCF de DataContractSerializer y proporcionar tipos de atributos usando KnownType.

+0

Olvidé mencionar que es necesario que sea texto para poder editarlo manualmente si es necesario, por lo que el binario no funciona. El DataContractSerializer se ve bien, pero miré a mi alrededor y no vi un ejemplo de serialización de una lista de tipo mixto al usarlo. ¡Gracias! – Steve

13

También puede usar XmlSerializer, pero debe incluir todos los tipos posibles que pueden aparecer en el gráfico de objetos que está serializando, lo que limita la extensibilidad y reduce el mantenimiento. Puede hacerlo mediante el uso de una sobrecarga del constructor de XmlSerializer:

var x = new XmlSerializer(animals.GetType(), new Type[] { typeof(Cat), typeof(Dog) }); 

Además, hay varias cuestiones de la nota cuando se usa XmlSerializer, toda la here (MSDN) esbozado - por ejemplo, busque bajo el título 'asambleas generados dinámicamente' .

+0

Lo intenté pero XmlSerializer aún falló porque la 'interfaz no se puede serializar'. No sabía este código para XmlSerializer, aprendí algo nuevo, gracias. – Steve

+5

@Steve, recomendaría crear una clase abstracta llamada Animal que implemente IAnimal. A continuación, podrá serializar la lista (suponiendo que modifique el constructor XmlSerializer y que Cat y Dog hereden la clase abstracta en lugar de IAnimal. Var animals = new List (); no es necesario que cambie. –

+1

@J. Mitchell ¡Muchas gracias por esto! Tuve el mismo problema ... – Ethenyl

7

El XmlSerializer no puede manejar una interfaz porque no sabe qué tipos crear al deserializar. Para evitar esto, debe manejar esa parte de la serialización implementando la interfaz IXmlSerializable. Esto le permite grabar el tipo para que pueda volver a crearlo (deserializarlo).

La clase ListOfIAnimal a continuación muestra cómo heredé y extendí la lista genérica List<IAnimal> para implementar la interfaz requerida. Comprimí tus viejas clases y agregué un campo extra sin interfaz para cada una de ellas, así pude ver que las clases concretas estaban siendo serializadas y deserializadas apropiadamente.

En comparación con su código, estoy usando el nuevo tipo ListOfIAnimal en lugar de List<IAnimal>, los otros cambios son solo un poco de refactorización.

Su código completo, simplemente cópielo en su propio archivo .cs, llame a la primera función para recorrerlo.

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using System.Xml; 
using System.Xml.Serialization; 

namespace Serialiser 
{ 
    static class SerialiseInterface 
    { 
     public static void SerialiseAnimals() 
     { 
      String finalXml; 

      // Serialize 
      { 
       var animals = new ListOfIAnimal{ 
        new Dog() { Age = 5, Teeth = 30 }, 
        new Cat() { Age = 6, Paws = 4 } 
       }; 

       var xmlSerializer = new XmlSerializer(animals.GetType()); 
       var stringBuilder = new StringBuilder(); 
       var xmlTextWriter = XmlTextWriter.Create(stringBuilder, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true }); 
       xmlSerializer.Serialize(xmlTextWriter, animals); 
       finalXml = stringBuilder.ToString(); 
      } 

      // Deserialise 
      { 
       var xmlSerializer = new XmlSerializer(typeof(ListOfIAnimal)); 
       var xmlReader = XmlReader.Create(new StringReader(finalXml)); 
       ListOfIAnimal animals = (ListOfIAnimal)xmlSerializer.Deserialize(xmlReader); 
      } 
     } 
    } 

    public class ListOfIAnimal : List<IAnimal>, IXmlSerializable 
    { 
     public ListOfIAnimal() : base() { } 

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

     public void ReadXml(XmlReader reader) 
     { 
      reader.ReadStartElement("ListOfIAnimal"); 
      while (reader.IsStartElement("IAnimal")) 
      { 
       Type type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName")); 
       XmlSerializer serial = new XmlSerializer(type); 

       reader.ReadStartElement("IAnimal"); 
       this.Add((IAnimal)serial.Deserialize(reader)); 
       reader.ReadEndElement(); //IAnimal 
      } 
      reader.ReadEndElement(); //ListOfIAnimal 
     } 

     public void WriteXml(XmlWriter writer) 
     { 
      foreach (IAnimal animal in this) 
      { 
       writer.WriteStartElement("IAnimal"); 
       writer.WriteAttributeString("AssemblyQualifiedName", animal.GetType().AssemblyQualifiedName); 
       XmlSerializer xmlSerializer = new XmlSerializer(animal.GetType()); 
       xmlSerializer.Serialize(writer, animal); 
       writer.WriteEndElement(); 
      } 
     } 
     #endregion 
    } 

    public interface IAnimal { int Age { get; set; } } 
    public class Dog : IAnimal { public int Age { get; set;} public int Teeth { get; set;} } 
    public class Cat : IAnimal { public int Age { get; set;} public int Paws { get; set;} } 
} 

pensé en dejar deserializar como ejercicio para el lector, pero el código would'n ser muy útil sin ella.

+0

No hay forma de hacerlo genéricamente ¿Por ejemplo, si Age in Dog y Cat no era solo int, sino que T, no hay forma de hacerlo? – user99999991

+1

@ user999999928 ver la respuesta aceptada de [xml-serialization-of-interface-property] (http://stackoverflow.com/questions/1333864/xml- serialization-of-interface-property) – nawfal

+0

Esto es genial. Me ayudó mucho, excepto una cosa. Agregar un miembro de esta clase a toda una estructura XML causará problemas. Dentro del método 'ReadXML' usted Agregue un 'if (reader.IsStartElement (" IAnimal ")' antes del ciclo while y termínelo después de la línea 'reader.ReadEndElement();'. De lo contrario, la serialización finalizará antes de que se complete todo el serializador xml – blackWorX

0

La manera fácil es agregar la decoración [Serializable()] a sus clases y cambiar su IList a List y ver si eso funciona.

Si usa interfaces, vaya a la respuesta del webturner.

3

Puede usar ExtendedXmlSerializer.

var serializer = new ExtendedXmlSerializer(); 
var xml = serializer.Serialize(animals); 

el código XML se verá así:

<?xml version="1.0" encoding="utf-8"?> 
<ArrayOfIAnimal> 
    <Dog type="Model.Dog" /> 
    <Cat type="Model.Cat" /> 
</ArrayOfIAnimal>