una serializar serialización objetos objeto lista convertir clase c# xml-serialization derived-class xml-deserialization

c# - serialización - ¿Cómo uso un XmlSerializer para deserializar un objeto que podría ser de una clase base o derivada sin conocer el tipo de antemano?



serializar una lista de objetos c# (5)

En C #, ¿cómo uso un XmlSerializer para deserializar un objeto que podría ser de una clase base, o de cualquiera de varias clases derivadas sin conocer el tipo de antemano?

Todas mis clases derivadas agregan miembros de datos adicionales. He creado una GUI simple que puede serializar y deserializar objetos de clase. Se serializarán los objetos, ya que cualquier clase heredada (o incluso solo la clase base) es apropiada en función de los campos que el usuario elija rellenar.

No tengo problemas con la serialización; El problema es la deserialización. ¿Cómo puedo tener el XmlSerializer deserializar los datos a la clase derivada correcta sin conocer la clase de antemano? Actualmente creo un XmlReader para leer el primer nodo del archivo XML y determinar la clase a partir de él, y parece funcionar para mis propósitos, pero parece una solución extremadamente poco elegante.

He publicado algunos ejemplos de código a continuación. ¿Alguna sugerencia?

BaseType objectOfConcern = new BaseType(); XmlSerializer xserializer; XmlTextReader xtextreader = new XmlTextReader(DEFAULT_FILENAME); do { xtextreader.Read(); } while (xtextreader.NodeType != XmlNodeType.Element); string objectType = xtextreader.Name; xtextreader.Close(); FileStream fstream = new FileStream(DEFAULT_FILENAME, FileMode.Open); switch (objectType) { case "type1": xserializer = new XmlSerializer(typeof(DerivedType)); objectOfConcern = (DerivedType)xserializer.Deserialize(fstream); //Load fields specific to that derived type here whatever = (objectOfConcern as DerivedType).NoOfstreamubordinates.ToString(); case "xxx_1": //code here case "xxx_2": //code here case "xxx_n": //code here //and so forth case "BaseType": xserializer = new XmlSerializer(typeof(BaseType)); AssignEventHandler(xserializer); objectOfConcern = (BaseType)xserializer.Deserialize(fstream); } //Assign all deserialized values from base class common to all derived classes here //Close the FileStream fstream.Close();


¿Tienes alguna clase / etiqueta raíz que contenga esos tipos derivados? Si es así, puede usar XmlElementAttribute para asignar el nombre de la etiqueta a escribir:

public class RootElementClass { [XmlElement(ElementName = "Derived1", Type = typeof(Derived1BaseType))] [XmlElement(ElementName = "Derived2", Type = typeof(Derived2BaseType))] [XmlElement(ElementName = "Derived3", Type = typeof(Derived3BaseType))] public BaseType MyProperty { get; set; } } public class BaseType { } public class Derived1BaseType : BaseType { } public class Derived2BaseType : BaseType { } public class Derived3BaseType : BaseType { }


Hace poco escribí este serializador / deserializador genérico para la clase base T y cualquier clase derivada de T. Parece que funciona hasta ahora.

La matriz Type [] almacena todos los tipos derivados de T y T en sí. El deserializador prueba cada uno de ellos y regresa cuando encuentra el correcto.

/// <summary> /// A generic serializer/deserializer /// </summary> /// <typeparam name="T"></typeparam> public static class Serializer<T> { /// <summary> /// serialize an instance to xml /// </summary> /// <param name="instance"> instance to serialize </param> /// <returns> instance as xml string </returns> public static string Serialize(T instance) { StringBuilder sb = new StringBuilder(); XmlWriterSettings settings = new XmlWriterSettings(); using (XmlWriter writer = XmlWriter.Create(sb, settings)) { XmlSerializer serializer = new XmlSerializer(instance.GetType()); serializer.Serialize(writer, instance); } return sb.ToString(); } /// <summary> /// deserialize an xml into an instance /// </summary> /// <param name="xml"> xml string </param> /// <returns> instance </returns> public static T Deserialize(string xml) { using (XmlReader reader = XmlReader.Create(new StringReader(xml))) { foreach (Type t in types) { XmlSerializer serializer = new XmlSerializer(t); if (serializer.CanDeserialize(reader)) return (T)serializer.Deserialize(reader); } } return default(T); } /// <summary> /// store all derived types of T: /// is used in deserialization /// </summary> private static Type[] types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(s => s.GetTypes()) .Where(t => typeof(T).IsAssignableFrom(t) && t.IsClass && !t.IsGenericType) .ToArray(); }



Si no está configurado para usar el XmlSerializer , puede usar el DataContractSerializer con el atributo KnownType lugar.

Todo lo que necesita hacer es agregar un atributo KnownType a la clase principal para cada subclase y el DataContractSerializer hará el resto.

El DataContractSerializer agregará información de tipo cuando se serialice a xml y usará esa información de tipo al deserializar para crear el tipo correcto.

Por ejemplo el siguiente código:

[KnownType( typeof( C2 ) )] [KnownType( typeof( C3 ) )] public class C1 {public string P1 {get;set;}} public class C2 :C1 {public string P2 {get;set;}} public class C3 :C1 {public string P3 {get;set;}} class Program { static void Main(string[] args) { var c1 = new C1{ P1="c1"}; var c2 = new C2{ P1="c1", P2="c2"}; var c3 = new C3{ P1="c1", P3="c3"}; var s = new DataContractSerializer( typeof( C1 ) ); Test( c1, s ); Test( c2, s ); Test( c3, s ); } static void Test( C1 objectToSerialize, DataContractSerializer serializer ) { using ( var stream = new MemoryStream() ) { serializer.WriteObject( stream, objectToSerialize ); stream.WriteTo( Console.OpenStandardOutput() ); stream.Position = 0; var deserialized = serializer.ReadObject( stream ); Console.WriteLine( Environment.NewLine + "Deserialized Type: " + deserialized.GetType().FullName ); } } }

Saldrá:

<C1 xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <P1>c1</P1></C1> Deserialized Type: ConsoleApplication1.C1 <C1 i:type="C2" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <P1>c1</P1><P2>c2</P2></C1> Deserialized Type: ConsoleApplication1.C2 <C1 i:type="C3" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <P1>c1</P1><P3>c3</P3></C1> Deserialized Type: ConsoleApplication1.C3

En la salida, notará que el xml para c2 y c3 contenía información de tipo adicional que permitió que DataContractSerializer.ReadObject creara el tipo correcto.


puedes usar XmlInclude

[XmlInclude(typeof(MyClass))] public abstract class MyBaseClass { //... }

de lo contrario, si desea agregar los tipos al serializar:

Type[] types = new Type[]{ typeof(MyClass) } XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);