2011-01-26 8 views
19

In C#, come si usa un XmlSerializer per deserializzare un oggetto che potrebbe essere di una classe base o di una qualsiasi delle varie classi derivate senza conoscere preventivamente il tipo?Come si utilizza un XmlSerializer per deserializzare un oggetto che potrebbe essere di una classe base o derivata senza conoscere preventivamente il tipo?

Tutte le mie classi derivate aggiungono membri dati aggiuntivi. Ho creato una semplice GUI che può serializzare e deserializzare oggetti di classe. Serializzerà gli oggetti come qualsiasi classe ereditata (o anche solo la classe base) è appropriata in base ai campi che l'utente sceglie di popolare.

Non ho problemi con la serializzazione; il problema è la deserializzazione. Come posso avere i dati deserialize XmlSerializer nella classe derivata corretta senza conoscere la classe in anticipo? Attualmente creo un XmlReader per leggere il primo nodo del file XML e determinare la classe da esso, e sembra funzionare per i miei scopi, ma sembra una soluzione estremamente inelegante.

Ho inserito qualche codice di esempio qui sotto. Eventuali suggerimenti?

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

risposta

17

Hai qualche tag/classe radice che contiene quei tipi derivati? Se sì, è possibile utilizzare XmlElementAttribute per mappare nome del tag di digitare:

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 { } 
+0

Very cool, grazie! –

4

Se non si imposta su utilizzando il XmlSerializer è possibile utilizzare il DataContractSerializer con l'attributo KnownType invece.

Tutto ciò che dovete fare è aggiungere un attributo KnownType alla classe genitore per ogni sottoclasse e il DataContractSerializer farà il resto.

Il DataContractSerializer aggiungerà informazioni sul tipo durante la serializzazione su xml e utilizzerà tale tipo di informazioni durante la deserializzazione per creare il tipo corretto.

Ad esempio, il seguente codice:

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

Will uscita:

<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 

In uscita si noterà la XML per C2 e C3 conteneva informazioni sul tipo di extra che ha permesso al DataContractSerializer.ReadObject per creare il tipo corretto

+0

+1 per un buon esempio. – Beyers

4

Ho recentemente scritto questo serializzatore generico \ deserializer per la classe base T e qualsiasi classe derivata di T. Sembra funzionare finora.

L'array Type [] memorizza tutti i tipi derivati ​​di T e T stesso. Il deserializzatore prova ciascuno di essi e ritorna quando trova quello giusto.

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

Esattamente quello che stavo cercando, grazie! – whywhywhy

2

è possibile utilizzare XmlInclude

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

altrimenti se si desidera aggiungere i tipi durante la serializzazione:

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

XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);