2009-02-26 16 views
10

Un proyecto base contiene una clase base abstracta Foo. En proyectos de cliente separados, hay clases que implementan esa clase base.Serialización y restauración de una clase desconocida

me gustaría para serializar y restaurar una instancia de una clase concreta llamando a algún método en la clase base:

// In the base project: 
public abstract class Foo 
{ 
    abstract void Save (string path); 
    abstract Foo Load (string path); 
} 

Se puede suponer que en el momento de deserialización, todas las clases necesarias están presentes . Si es posible de alguna manera, la serialización debe hacerse en XML. Hacer que la clase base implemente IXmlSerializable es posible.

Estoy un poco atrapado aquí. Si mi comprensión de las cosas es correcta, esto solo es posible si agrego un [XmlInclude(typeof(UnknownClass))] a la clase base para cada clase de implementación, ¡pero las clases de implementación son desconocidas!

¿Hay alguna manera de hacerlo? No tengo experiencia con la reflexión, pero también me gusta usar las respuestas.

Edit: El problema es De serialización. Solo serializar sería algo fácil. :-)

Respuesta

9

También puede hacer esto en el momento de la creación de un XmlSerializer, proporcionando los detalles adicionales en el constructor. Tenga en cuenta que no vuelve a utilizar dichos modelos, por lo que querrá configurar el XmlSerializer una vez (al inicio de la aplicación, desde la configuración) y volver a usarlo repetidamente ... tenga en cuenta que es posible realizar muchas más personalizaciones con la sobrecarga XmlAttributeOverrides ...

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Xml.Serialization; 
static class Program 
{ 
    static readonly XmlSerializer ser; 
    static Program() 
    { 
     List<Type> extraTypes = new List<Type>(); 
     // TODO: read config, or use reflection to 
     // look at all assemblies 
     extraTypes.Add(typeof(Bar)); 
     ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray()); 
    } 
    static void Main() 
    { 
     Foo foo = new Bar(); 
     MemoryStream ms = new MemoryStream(); 
     ser.Serialize(ms, foo); 
     ms.Position = 0; 
     Foo clone = (Foo)ser.Deserialize(ms); 
     Console.WriteLine(clone.GetType()); 
    } 
} 

public abstract class Foo { } 
public class Bar : Foo {} 
1

En algún lugar profundo de los espacios de nombres XML se encuentra una maravillosa clase llamada XmlReflectionImporter.

Esto puede ser útil si necesita crear un esquema en tiempo de ejecución.

1

También puede hacerlo creando un passign XmlSerializer en todos los tipos posibles al constructor. Tenga en cuenta que cuando use este constructor el xmlSerializer se compilará todas las veces y dará como resultado una fuga si lo recrea constantemente. Querrá crear un único serializador y reutilizarlo en su aplicación.

A continuación, puede iniciar el serializador y utilizar el aspecto de reflexión para cualquier descendiente de foo.

2

Bueno, la serialización no debería ser un problema, el constructor XmlSerializer toma un argumento Tipo, incluso llamando a GetType en una instancia de una clase derivada mediante un método en la base abstracta devolverá los tipos derivados tipo real. Así que, en esencia, siempre y cuando conozca el tipo apropiado después de la deserialización, la serialización del tipo apropiado es trivial. Así se puede implementar un método en la base llamada serializar o lo que sea que pasa this.GetType() al constructor de XmlSerializer .. o simplemente pasa a la referencia actual y permite que el método serialize cuidar de él y que debe estar bien.

Editar: Actualización para OP Editar ..

Si usted no sabe el tipo de deserialización entonces realmente tiene nada más que una matriz de cadenas o byte, sin algún tipo de identificador en algún lugar que es tipo de una Arroyo. Hay algunas cosas que puedes hacer para tratar de deserializar, ya que cada tipo derivado conocido de la clase base xx, no lo recomendaría.

Su otra opción es recorrer el XML manualmente y reconstruir un objeto incrustando el tipo como una propiedad o lo que tiene, tal vez eso es lo que originalmente quiso decir en el artículo, pero tal como está, no creo que haya es una forma de que la serialización incorporada se encargue de esto sin que usted especifique el tipo.

3

No tiene que poner las funciones de serialización en ninguna clase base, en su lugar, puede agregarla a su clase de utilidades.

p. Ej. (El código es sólo un ejemplo, RootName es opcional)

public static class Utility 
{ 
     public static void ToXml<T>(T src, string rootName, string fileName) where T : class, new() 
     { 
      XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName)); 
      XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8); 
      serializer.Serialize(writer, src); 
      writer.Flush(); 
      writer.Close(); 
     } 
} 

simplemente hacer llamada a

Utility.ToXml(fooObj, "Foo", @"c:\foo.xml"); 

No sólo los tipos de familia de Foo pueden usarlo, pero todos los demás objetos serializables.

EDITAR

servicio completo OK ... (nombre raíz es opcional)

public static T FromXml<T>(T src, string rootName, string fileName) where T : class, new() 
{ 
    XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName)); 
    TextReader reader = new StreamReader(fileName); 
    return serializer.Deserialize(reader) as T; 
} 
+1

di cuenta que es muy viejo, pero parece una gran solución. Me pregunto cómo usar la función FromXml, ya que no está del todo claro: la 'T src' no se usa y requiere una clase para crear una instancia. Al eliminarlo, el compilador tiene dificultades para descubrir qué es T. –

0

Marcado de las clases como Serializable y el uso de jabón BinaryFormatter en lugar de XmlSerializer le dará esta funcionalidad de forma automática. Al serializar la información de tipo de la instancia que se está serializando se escribirá en el XML, y Soap BinaryFormatter puede instanciar las subclases al deserializar.

+0

Que oficialmente está obsoleto: "A partir de .NET Framework versión 3.5, esta clase está obsoleta. Use BinaryFormatter en su lugar."; http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.soap.soapformatter.aspx –

+0

Cambié la respuesta en consecuencia. Gracias. –

+0

Me temo que BinaryFormatter no produce salida XML, sin embargo. : - \ – mafu

1

Estos enlaces probablemente será útil para usted:

Tengo un proyecto de interacción remota compleja y querían un control muy estricto sobre el XML serializado. El servidor podría recibir objetos que no tenía idea de cómo deserializar y viceversa, así que necesitaba una forma de identificarlos rápidamente.

Todas las soluciones .NET que probé carecían de la flexibilidad necesaria para mi proyecto.

Guardo un atributo int en el xml base para identificar el tipo de objeto.

Si necesito crear un objeto nuevo a partir de xml, creé una clase de fábrica que verifica el atributo de tipo y luego crea la clase derivada apropiada y le da el xml.

he hecho algo como esto (tirando esta fuera de la memoria, por lo que la sintaxis puede ser un poco apagado):

(1) Creado una interfaz

interface ISerialize 
{ 
    string ToXml(); 
    void FromXml(string xml);  
}; 

(2) Clase base

public class Base : ISerialize 
{ 
    public enum Type 
    { 
     Base, 
     Derived 
    }; 

    public Type m_type; 

    public Base() 
    { 
     m_type = Type.Base; 
    } 

    public virtual string ToXml() 
    { 
     string xml; 
     // Serialize class Base to XML 
     return string; 
    } 

    public virtual void FromXml(string xml) 
    { 
     // Update object Base from xml 
    } 
}; 

(3) clase derivada

public class Derived : Base, ISerialize 
{ 
    public Derived() 
    { 
     m_type = Type.Derived; 
    } 

    public override virtual string ToXml() 
    { 
     string xml; 
     // Serialize class Base to XML 
     xml = base.ToXml(); 
     // Now serialize Derived to XML 
     return string; 
    } 
    public override virtual void FromXml(string xml) 
    { 
     // Update object Base from xml 
     base.FromXml(xml); 
     // Update Derived from xml 
    } 
}; 

(4) fábrica Objeto

public ObjectFactory 
{ 
    public static Base Create(string xml) 
    { 
     Base o = null; 

     Base.Type t; 

     // Extract Base.Type from xml 

     switch(t) 
     { 
      case Base.Type.Derived: 
       o = new Derived(); 
       o.FromXml(xml); 
      break; 
     } 

     return o; 
    } 
}; 
0

Este método lee el elemento raíz XML y comprueba si el conjunto de ejecución actual contiene un tipo con tal nombre. Si es así, el documento XML está deserializado. Si no, se produce un error.

public static T FromXml<T>(string xmlString) 
{ 
    Type sourceType; 
    using (var stringReader = new StringReader(xmlString)) 
    { 
     var rootNodeName = XElement.Load(stringReader).Name.LocalName; 
     sourceType = 
       Assembly.GetExecutingAssembly().GetTypes() 
        .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
            && t.Name == rootNodeName) 
       ?? 
       Assembly.GetAssembly(typeof(T)).GetTypes() 
        .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
            && t.Name == rootNodeName); 

     if (sourceType == null) 
     { 
      throw new Exception(); 
     } 
    } 

    using (var stringReader = new StringReader(xmlString)) 
    { 
     if (sourceType.IsSubclassOf(typeof(T)) || sourceType == typeof(T)) 
     { 
      var ser = new XmlSerializer(sourceType); 

      using (var xmlReader = new XmlTextReader(stringReader)) 
      { 
       T obj; 
       obj = (T)ser.Deserialize(xmlReader); 
       xmlReader.Close(); 
       return obj; 
      } 
     } 
     else 
     { 
      throw new InvalidCastException(sourceType.FullName 
              + " cannot be cast to " 
              + typeof(T).FullName); 
     } 
    } 
} 
0

Utilicé el atributo XmlType de las clases desconocidas (pero esperadas) para determinar el tipo para la deserialización. Los tipos esperados se cargan durante la instanciación de la clase AbstractXmlSerializer y se colocan en un diccionario. Durante la deserialización se lee el elemento raíz y con esto el tipo se recupera del diccionario. Después de eso, puede deserializarse normalmente.

XmlMessage.class:

public abstract class XmlMessage 
{ 
} 

IdleMessage.class:

[XmlType("idle")] 
public class IdleMessage : XmlMessage 
{ 
    [XmlElement(ElementName = "id", IsNullable = true)] 
    public string MessageId 
    { 
     get; 
     set; 
    } 
} 

AbstractXmlSerializer.class:

public class AbstractXmlSerializer<AbstractType> where AbstractType : class 
{ 
    private Dictionary<String, Type> typeMap; 

    public AbstractXmlSerializer(List<Type> types) 
    {    
     typeMap = new Dictionary<string, Type>(); 

     foreach (Type type in types) 
     { 
      if (type.IsSubclassOf(typeof(AbstractType))) { 
       object[] attributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), false); 

       if (attributes != null && attributes.Count() > 0) 
       { 
        XmlTypeAttribute attribute = attributes[0] as XmlTypeAttribute; 
        typeMap[attribute.TypeName] = type; 
       } 
      } 
     } 
    } 

    public AbstractType Deserialize(String xmlData) 
    { 
     if (string.IsNullOrEmpty(xmlData)) 
     { 
      throw new ArgumentException("xmlData parameter must contain xml"); 
     }    

     // Read the Data, Deserializing based on the (now known) concrete type. 
     using (StringReader stringReader = new StringReader(xmlData)) 
     { 
      using (XmlReader xmlReader = XmlReader.Create(stringReader)) 
      { 
       String targetType = GetRootElementName(xmlReader); 

       if (targetType == null) 
       { 
        throw new InvalidOperationException("XML root element was not found"); 
       }       

       AbstractType result = (AbstractType)new 
        XmlSerializer(typeMap[targetType]).Deserialize(xmlReader); 
       return result; 
      } 
     } 
    } 

    private static string GetRootElementName(XmlReader xmlReader) 
    {    
     if (xmlReader.IsStartElement()) 
     { 
      return xmlReader.Name; 
     } 

     return null; 
    } 
} 

unittest:

[TestMethod] 
public void TestMethod1() 
{ 
    List<Type> extraTypes = new List<Type>(); 
    extraTypes.Add(typeof(IdleMessage)); 
    AbstractXmlSerializer<XmlMessage> ser = new AbstractXmlSerializer<XmlMessage>(extraTypes); 

    String xmlMsg = "<idle></idle>"; 

    MutcMessage result = ser.Deserialize(xmlMsg); 
    Assert.IsTrue(result is IdleMessage);   
} 
Cuestiones relacionadas