XmlSerializer serializza Elenco di interfacce generico

Sto cercando di usare XmlSerializer per mantenere un elenco (T) in cui T è un’interfaccia. Il serializzatore non ama le interfacce. Sono curioso di sapere se esiste un modo semplice per serializzare facilmente un elenco di oggetti eterogenei con XmlSerializer. Ecco cosa sto andando per:

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

Puoi anche usare XmlSerializer, ma devi includere tutti i possibili tipi che possono apparire nel grafico degli oggetti che stai serializzando, il che limita l’estensibilità e riduce la manutenibilità. Puoi farlo usando un overload del costruttore di XmlSerializer:

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

Inoltre, quando si utilizza XmlSerializer, tutti i file delineati (MSDN) presentano diversi problemi, ad esempio sotto l’intestazione “Assiemi generati dynamicmente”.

XmlSerializer non può gestire un’interfaccia perché non sa quali tipi creare durante la deserializzazione. Per aggirare questo problema è necessario gestire personalmente questa parte della serializzazione implementando l’interfaccia IXmlSerializable . Ciò consente di registrare il tipo in modo da poterlo ricreare (deserializzare).

La class ListOfIAnimal riportata di seguito mostra come ho ereditato ed esteso l’elenco di List generici List per implementare l’interfaccia richiesta. Ho aumentato le tue vecchie classi aggiungendo un campo extra non di interfaccia a ciascuna, così ho potuto vedere che le classi concrete venivano serializzate e deserializzate correttamente.

Rispetto al tuo codice sto semplicemente usando il nuovo tipo ListOfIAnimal al posto di List , le altre modifiche sono solo un po ‘di refactoring.

Il suo codice completo, basta copiarlo nel proprio file .cs, chiamare la prima funzione per passarci attraverso.

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

Ho pensato di lasciare la deserializzazione come esercizio per il lettore, ma il codice sarebbe stato molto utile senza di esso.

Devi usare XmlSerializer ? Questo è un problema noto con XmlSerializer .

Puoi usare BinaryFormatter per salvare su uno stream:

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

Un’altra alternativa è usare DataContractSerializer di WCF e fornire tipi usando l’attributo KnownType.

È ansible utilizzare ExtendedXmlSerializer .

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

Il tuo xml sarà simile a:

      

Il modo semplice è aggiungere la decorazione [Serializable ()] alle tue classi e cambiare il tuo IList in List e vedere se funziona.

Se si utilizzano interfacce, consultare la risposta di webturner.