Domanda Serializzazione e ripristino di una classe sconosciuta


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

Mi piacerebbe serializzare e ripristinare un'istanza di una classe concreta chiamando un metodo sulla classe 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 siano presenti tutte le classi necessarie. 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, allora è 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 fare questo? Non ho esperienza con la riflessione, ma accolgo anche le risposte che lo utilizzano.

Modificare: Il problema è deserializzazione. Solo la serializzazione sarebbe facile. :-)


10
2018-02-26 14:00


origine


risposte:


Puoi anche farlo al momento della creazione di 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 riutilizzalo ripetutamente ... nota che sono possibili molte altre personalizzazioni con XmlAttributeOverrides sovraccarico...

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

9
2018-02-26 14:12



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

per esempio. (il codice è solo per esempio, rootName è facoltativo)

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

Basta chiamare a

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

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

MODIFICARE

OK servizio completo ... (rootName è facoltativo)

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

3
2018-02-26 14:12



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 passato this.GetType() al costruttore di XmlSerializer .. o semplicemente passa il riferimento corrente e lascia che il metodo serialize si prenda cura di esso e si dovrebbe andare bene.

Modifica: Aggiorna per OP Modifica ..

Se non conosci il tipo alla deserializzazione, in realtà non hai nient'altro che una stringa o una matrice di byte, senza una sorta di identificatore da qualche parte in cui sei un po 'su 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.

La tua altra opzione è quella di camminare manualmente 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 penso che ci sia un modo per la serializzazione integrata per occuparti di questo per te senza che tu specifichi il tipo.


2
2018-02-26 14:10



Da qualche parte nel profondo dei namespace XML c'è una classe meravigliosa chiamata XmlReflectionImporter.

Questo può esserti d'aiuto se devi creare uno schema in fase di runtime.


1
2018-02-26 14:04



Puoi anche farlo creando un passign XmlSerializer in tutti i possibili tipi al costruttore. 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.


1
2018-02-26 14:10



Questi collegamenti ti saranno probabilmente utili:

Ho un complesso progetto remoto e volevo un controllo molto stretto sull'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 (estraendo questa memoria, quindi la sintassi potrebbe essere un po 'spenta):

(1) Creata 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 di oggetti

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

1
2018-02-26 14:24



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

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
2017-12-06 17:03



Contrassegnare le classi come serializzabili e utilizzabili SaponeBinaryFormatter invece di XmlSerializer ti darà questa funzionalità automaticamente. Durante la serializzazione delle informazioni sul tipo dell'istanza in fase di serializzazione verrà scritto nell'XML e SaponeBinaryFormatter può creare un'istanza delle sottoclassi durante la deserializzazione.


0
2018-02-26 14:17



Ho usato 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);           
}

0
2017-12-08 08:51