Сериализация и восстановление неизвестного класса

Базовый проект содержит абстрактный базовый класс 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. Просто сериализация была бы легкой.: -)

Ответ 1

Вы также можете сделать это в момент создания 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 {}

Ответ 2

Вам не нужно включать функции сериализации в любой базовый класс, вместо этого вы можете добавить его в свой класс Utility.

например. (только код, например, 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, но и все другие сериализуемые объекты.

ИЗМЕНИТЬ

OK полный сервис... (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;
}

Ответ 3

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

Изменить: обновить для OP Edit..

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Если мне нужно создать новый объект из xml, я создал класс factory, который проверяет атрибут 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) Объект factory

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

Ответ 7

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

Ответ 8

Этот метод считывает корневой элемент 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);
        }
    }
}

Ответ 9

Я использовал атрибут 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);           
}