2009-02-26 3 views
10

Un progetto di base contiene una classe base astratta Foo. In progetti client separati, ci sono classi che implementano quella classe base.Serializzazione e ripristino di una classe sconosciuta

mi piacerebbe serializzare e il ripristino di un'istanza di una classe concreta chiamando un metodo sulla classe di base:

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

Si può presumere che, al momento della deserializzazione, tutte le classi necessarie sono presenti . Se possibile, la serializzazione dovrebbe essere eseguita in XML. È possibile rendere la classe base implementare IXmlSerializable.

Sono un po 'bloccato qui. Se la mia comprensione delle cose è corretta, questo è possibile solo aggiungendo un [XmlInclude(typeof(UnknownClass))] alla classe base per ogni classe di implementazione, ma le classi di implementazione sono sconosciute!

C'è un modo per farlo? Non ho esperienza con la riflessione, ma accolgo anche le risposte che lo utilizzano.

Modifica: Il problema è di serializzazione. Solo la serializzazione sarebbe facile. :-)

risposta

9

Si può anche fare questo al punto di creare un XmlSerializer, fornendo i dettagli aggiuntivi nel costruttore. Si noti che non riutilizza tali modelli, quindi si consiglia di configurare il XmlSerializer una volta (all'avvio dell'app, dalla configurazione) e riutilizzarlo ripetutamente ... si notano molte più personalizzazioni possibili con il sovraccarico 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

Da qualche parte nel profondo dei namespace XML si trova una classe meravigliosa chiamata XmlReflectionImporter.

Questo può essere di aiuto se è necessario creare uno schema in fase di esecuzione.

1

È anche possibile eseguire questa operazione creando un passign XmlSerializer in tutti i tipi possibili su constructor. Tieni presente che quando si utilizza questo costruttore, xmlSerializer verrà compilato ogni volta e provocherà una perdita se lo si ricrea costantemente. Dovrai creare un singolo serializzatore e riutilizzarlo nella tua applicazione.

È quindi possibile eseguire il bootstrap del serializzatore e utilizzare l'aspetto reflection per qualsiasi discendente di foo.

2

Bene, la serializzazione non dovrebbe essere un problema, il costruttore XmlSerializer accetta un argomento Type, anche chiamando GetType su un'istanza di una classe derivata attraverso un metodo sulla base astratta restituirà i tipi derivati ​​di tipo effettivo. Quindi, in sostanza, se si conosce il tipo corretto dopo la deserializzazione, la serializzazione del tipo corretto è banale. Quindi puoi implementare un metodo sulla base chiamato serialize o cosa hai che passa this.GetType() al costruttore di XmlSerializer .. o semplicemente passa il riferimento corrente e lascia che il metodo serialize si prenda cura di esso e tu dovresti stare bene.

Edit: Aggiornamento per OP Edit ..

Se non si conosce il tipo a deserializzazione allora davvero non hanno nulla, ma una matrice di stringhe o di byte, senza una sorta di identificativo da qualche parte sei gentile di un torrente. Ci sono alcune cose che puoi fare come provare a deserializzare come ogni tipo derivato noto della classe base xx, non lo consiglierei.

L'altra opzione è quella di eseguire manualmente l'XML e ricostruire un oggetto incorporando il tipo come proprietà o cosa hai, forse questo è ciò che originariamente intendevi nell'articolo, ma così com'è non ci penso è un modo per la serializzazione integrata di occuparsi di questo per te senza che tu specifichi il tipo.

3

Non è necessario inserire le funzioni di serializzazione in alcuna classe base, ma è possibile aggiungerla alla propria classe di utilità.

ad es. (Il codice è solo per esempio, RootName è opzionale)

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

semplicemente fare chiamata a

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

Non solo tipi di famiglia di Foo possono usarlo, ma tutti gli altri oggetti serializzabili.

EDIT

OK servizio completo ... (nome radice è opzionale)

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

rendo conto che è molto vecchio, ma si presenta come una grande soluzione. Mi chiedo solo come usare la funzione FromXml come non del tutto chiara: "T src" non è usato e richiede una istanza di classe. Rimuovendolo, il compilatore fatica a capire cosa sia T. –

0

Contrassegno delle classi Serializable e l'utilizzo di Sapone BinaryFormatter invece di XmlSerializer vi darà questa funzionalità automaticamente. Quando si serializzano le informazioni sul tipo dell'istanza in fase di serializzazione, verrà scritto nell'XML e Soap BinaryFormatter può creare un'istanza delle sottoclassi durante la deserializzazione.

+0

Che è ufficialmente obsoleto: "A partire da .NET Framework versione 3.5, questa classe è obsoleta. Utilizza invece BinaryFormatter."; http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.soap.soapformatter.aspx –

+0

Ho modificato la risposta di conseguenza. Grazie. –

+0

Ho paura che BinaryFormatter non produca output XML, comunque. : - \ – mafu

1

Questi collegamenti saranno probabilmente utile a voi:

Ho un progetto Remoting complessa e voleva il controllo molto stretto sul XML serializzato. Il server poteva ricevere oggetti che non aveva idea di come deserializzare e viceversa, quindi avevo bisogno di un modo per identificarli rapidamente.

Tutte le soluzioni .NET che ho provato mancavano della flessibilità necessaria per il mio progetto.

Memorizzo un attributo int nel xml di base per identificare il tipo di oggetto.

Se ho bisogno di creare un nuovo oggetto da xml, ho creato una classe factory che controlla l'attributo type, quindi crea la classe derivata appropriata e la alimenta con xml.

ho fatto qualcosa di simile (tirando questo fuori di memoria, quindi la sintassi può essere un po 'fuori):

(1) Creato un'interfaccia

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

(2) Classe 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) classe derivata

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) fabbrica oggetto

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

Questo metodo legge l'elemento principale XML e controlla se l'assemblea esecuzione corrente contiene un tipo con un tale nome. In tal caso, il documento XML viene deserializzato. In caso contrario, viene generato un errore.

0

Ho utilizzato l'attributo XmlType delle classi sconosciute (ma previste) per determinare il Tipo per la deserializzazione. I tipi previsti sono caricati durante l'istanziazione della classe AbstractXmlSerializer e inseriti in un dizionario. Durante la deserializzazione viene letto l'elemento radice e con questo il tipo viene recuperato dal dizionario. Dopo di che può essere deserializzato 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);   
}