¿Cómo utilizo un XmlSerializer para deserializar un objeto que podría ser de una base o clase derivada sin conocer de antemano el tipo?

votos
20

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

Todas mis clases derivadas añadir miembros de datos adicionales. He hecho una interfaz gráfica de usuario simple que puede serializar y deserializar objetos de clase. Será serializar objetos como cualquier clase heredada (o incluso sólo la clase base) es adecuado en función de los campos que el usuario elige para poblar.

No tengo problemas con la serialización; el problema es la deserialización. ¿Cómo es posible que los XmlSerializerdatos de deserializar a la clase derivada correcta sin conocer de antemano la clase? Actualmente creo una XmlReaderpara leer el primer nodo del archivo XML y determinar la clase de ella, y parece que funciona para mis propósitos, pero parece una solución muy poco elegante.

He publicado un código de ejemplo 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();
Publicado el 26/01/2011 a las 04:40
fuente por usuario
En otros idiomas...                            


5 respuestas

votos
17

Usted tiene algún / etiqueta de clase raíz que contiene que los tipos de derivados? Si es así, puede utilizar XmlElementAttribute para asignar nombre de la etiqueta de tipo:

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 { }
Respondida el 26/01/2011 a las 05:23
fuente por usuario

votos
5

Hace poco escribí este genérico serializador \ deserializer para la clase base T y las clases derivadas de T. parece funcionar hasta el momento.

El tipo [] almacena matriz todos los tipos derivados de T y la propia T. El deserializer trata cada uno de ellos, y devuelve cuando se encontró la correcta.

/// <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();
}
Respondida el 08/03/2014 a las 02:11
fuente por usuario

votos
4

Si usted no está configurado en el uso de la XmlSerializerpuedes usar el DataContractSerializercon el KnownTypeatributo en su lugar.

Todo lo que necesita hacer es añadir un KnownTypeatributo a la clase padre para cada subclase y la DataContractSerializerhará el resto.

La DataContractSerializerañadirá información de tipo al serializar a XML y utilizar la información de tipos al deserializar para crear el tipo correcto.

Por ejemplo, el código siguiente:

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

es la salida:

<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 se dará cuenta el xml para C2 y C3 contenía información de tipo adicional que permitió que el DataContractSerializer.ReadObjectpara crear el tipo correcto.

Respondida el 27/01/2011 a las 06:02
fuente por usuario

votos
3

Se podría tratar de utilizar el constructor XmlSerializer (Tipo Tipo, escriba [] extraTypes) para crear un serializador que funciona con todos los tipos involucrados.

Respondida el 26/01/2011 a las 05:01
fuente por usuario

votos
2

puede utilizar XmlInclude

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

de lo contrario si desea agregar los tipos Cuando seria:

Type[] types = new Type[]{ typeof(MyClass) }

XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);
Respondida el 07/01/2016 a las 11:13
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more