序列化和恢复未知类

时间:2009-02-26 14:00:05

标签: c# xml-serialization abstract-class

基础项目包含一个抽象基类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 个答案:

答案 0 :(得分:9)

您还可以在创建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 {}

答案 1 :(得分:3)

您不必将序列化函数放入任何基类,而是可以将其添加到Utility类中。

e.g。 (代码仅供参考,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的家庭类型可以使用它,还有所有其他可序列化的对象。

修改

确定完整服务...(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 :(得分:2)

序列化应该不是问题,XmlSerializer构造函数采用Type参数,甚至通过抽象基础上的方法在派生类的实例上调用GetType将返回派生类型的实际Type。所以从本质上讲,只要你知道反序列化时的正确类型,那么正确类型的序列化是微不足道的。所以你可以在名为serialize的基础上实现一个方法,或者你有什么把this.GetType()传递给XmlSerializer的构造函数..或者只是传递当前的引用并让serialize方法处理它你应该是细

编辑:OP编辑的更新..

如果你不知道反序列化的类型那么你真的只有一个字符串或字节数组,没有某种标识符,你在某个小溪上。你可以做一些事情,比如尝试反序列化为xx基类的每个已知派生类型,我不建议这样做。

您的另一个选择是手动遍历XML并通过将类型作为属性或者您拥有的内容重新构建对象,也许这就是您最初在文章中的意思,但是现在我认为没有内置序列化的一种方法,可以在不指定类型的情况下为您处理此问题。

答案 3 :(得分:1)

XML命名空间内部的某个位置是一个名为XmlReflectionImporter的奇妙类。

如果您需要在运行时创建架构,这可能对您有所帮助。

答案 4 :(得分:1)

您也可以通过为constructor创建所有可能类型的XmlSerializer密码来完成此操作。请注意,当您使用此构造函数时,xmlSerializer将每次都被编译,如果您不断重新创建它将导致泄漏。您将需要创建一个序列化程序并在应用程序中重用它。

然后,您可以引导序列化程序并使用反射查找foo的任何后代。

答案 5 :(得分:1)

这些链接可能对您有所帮助:

我有一个复杂的远程处理项目,并希望对序列化的XML进行非常严格的控制。服务器可以接收不知道如何反序列化的对象,反之亦然,所以我需要一种方法来快速识别它们。

我尝试的所有.NET解决方案都缺乏我项目所需的灵活性。

我在基础xml中存储一个int属性来标识对象的类型。

如果我需要从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;
    }
};

答案 6 :(得分:0)

将类标记为Serializable并使用 Soap BinaryFormatter而不是XmlSerializer将自动为您提供此功能。序列化序列化实例的类型信息时,将写入XML, Soap BinaryFormatter可以在反序列化时实例化子类。

答案 7 :(得分:0)

此方法读取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);
        }
    }
}

答案 8 :(得分:0)

我使用了未知(但是预期)类的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;
    }
}

<强>单元测试

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