Сериализация и восстановление неизвестного класса - PullRequest
11 голосов
/ 26 февраля 2009

Базовый проект содержит абстрактный базовый класс Foo. В отдельных клиентских проектах есть классы, реализующие этот базовый класс.

Я хотел бы сериализовать и восстановить экземпляр конкретного класса, вызвав некоторый метод базового класса:

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

Можно предположить, что во время десериализации присутствуют все необходимые классы. Если это возможно, сериализация должна выполняться в XML. Возможно сделать базовый класс реализующим IXmlSerializable.

Я немного застрял здесь. Если мое понимание вещей верно, то это возможно только путем добавления [XmlInclude(typeof(UnknownClass))] к базовому классу для каждого реализующего класса - но реализующие классы неизвестны!

Есть ли способ сделать это? У меня нет опыта рефлексии, но я также приветствую ответы, используя его.

Редактировать: Проблема в De сериализации. Просто сериализация была бы легкой. : -)

Ответы [ 9 ]

9 голосов
/ 26 февраля 2009

Вы также можете сделать это в момент создания XmlSerializer, предоставив дополнительные детали в конструкторе. Обратите внимание, что он не использует такие модели повторно, поэтому вам нужно настроить XmlSerializer один раз (при запуске приложения, из конфигурации) и использовать его повторно несколько раз ... обратите внимание, что с помощью 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 {}
3 голосов
/ 26 февраля 2009

Вам не нужно помещать функции сериализации в какой-либо базовый класс, вместо этого вы можете добавить их в свой класс утилит.

например. (код только для примера, rootName не обязателен)

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

Просто позвоните на

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

Его могут использовать не только типы семейства Foo, но и все другие сериализуемые объекты.

EDIT

ОК, полный сервис ... (rootName необязательно)

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;
}
2 голосов
/ 26 февраля 2009

Что ж, сериализация не должна быть проблемой, конструктор XmlSerializer принимает аргумент Type, даже вызывая GetType для экземпляра производного класса через метод на абстрактной основе, будет возвращать фактический тип производных типов. Таким образом, по существу, если вы знаете правильный тип при десериализации, то сериализация правильного типа тривиальна. Таким образом, вы можете реализовать метод на базе, называемый serialize или что-либо еще, что передает this.GetType() в конструктор XmlSerializer .. или просто передает текущую ссылку и позволяет методу serialize позаботиться об этом, и у вас все будет хорошо.

Edit: обновление для OP Edit ..

Если вы не знаете тип при десериализации, тогда у вас действительно нет ничего, кроме строки или байтового массива, без какого-либо идентификатора, где-то, вы как бы ручей. Есть некоторые вещи, которые вы можете сделать, например, попытаться десериализовать как каждый известный производный тип базового класса xx, я бы не рекомендовал это.

Другой вариант - вручную пройтись по XML и реконструировать объект, внедрив тип в качестве свойства или того, что у вас есть. Может быть, это то, что вы изначально имели в виду в статье, но в нынешнем виде я не думаю, что есть способ для встроенной сериализации позаботиться об этом для вас без указания типа.

1 голос
/ 26 февраля 2009

Эти ссылки, вероятно, будут вам полезны:

У меня сложный проект удаленного взаимодействия, и я хотел очень жестко контролировать сериализованный XML. Сервер мог получать объекты, которые он не знал, как десериализовать, и наоборот, поэтому мне нужен был способ их быстрой идентификации.

Всем .NET-решениям, которые я пробовал, не хватало необходимой гибкости для моего проекта.

Я храню атрибут int в базовом xml для идентификации типа объекта.

Если мне нужно создать новый объект из xml, я создал фабричный класс, который проверяет атрибут type, затем создает соответствующий производный класс и передает ему xml.

Я сделал что-то вроде этого (вытащил это из памяти, так что синтаксис может быть немного не так):

(1) Создан интерфейс

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

(2) Базовый класс

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) Производный класс

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) Объект фабрики

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 голос
/ 26 февраля 2009

Вы также можете сделать это, создав партию XmlSerializer во всех возможных типах для конструктора . Имейте в виду, что при использовании этого конструктора xmlSerializer будет компилироваться каждый раз и приведет к утечке, если вы будете постоянно его воссоздавать. Вы захотите создать один сериализатор и повторно использовать его в своем приложении.

Затем вы можете запустить сериализатор и, используя отражение, найти любых потомков foo.

1 голос
/ 26 февраля 2009

Где-то глубоко внутри пространств имен XML находится замечательный класс XmlReflectionImporter.

Это может помочь вам, если вам нужно создать схему во время выполнения.

0 голосов
/ 08 декабря 2016

Я использовал атрибут XmlType неизвестных (но ожидаемых) классов, чтобы определить тип для десериализации. Ожидаемые типы загружаются во время создания класса AbstractXmlSerializer и помещаются в словарь. Во время десериализации читается корневой элемент, и при этом тип извлекается из словаря. После этого его можно десериализовать как обычно.

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 голосов
/ 06 декабря 2011

Этот метод читает корневой элемент XML и проверяет, содержит ли текущая исполняемая сборка тип с таким именем. Если это так, документ XML десериализован. Если нет, выдается ошибка.

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 голосов
/ 26 февраля 2009

Пометка классов как Serializable и использование Soap BinaryFormatter вместо XmlSerializer предоставит вам эту функциональность автоматически. При сериализации информация о типе сериализуемого экземпляра будет записана в XML, и Soap BinaryFormatter может создавать экземпляры подклассов при десериализации.

...