如何序列化包含其他类对象的类(递归序列化?)

16

如何做到这一点?或者序列化程序是否会自动进行递归,并将所有子对象序列化为 XML?

请给我一个示例,说明如何序列化包含其他类对象的类!这是这个问题的核心!

我尝试过这样做,但它没有输出任何东西(除了 XML 头)到目标 XML 文件。

我的问题是,我需要序列化一个简单的类,它只包含一个 List 对象。但是,这些实体也包含 List 对象。(如果我能避免对某些组件进行序列化,那就更好了,因为有些组件是派生的,并且其中还有字典。)

public void SaveCurrent(string MapFileName)
{
    string MapPath = world_.game_.Content.RootDirectory + "/Maps/" + MapFileName + ".xml";
    StreamWriter MapWriter = new StreamWriter(MapPath);

    Map SavedMap = new Map();
    SavedMap.Entities = world_.Entities;
    XmlSerializer xSerializer = new XmlSerializer(SavedMap.GetType());

    xSerializer.Serialize(MapWriter, SavedMap);
    MapWriter.Close();
}

这就是进行序列化的代码片段。

public class Map
{
    internal string MapName;
    internal string MapDescription;
    internal string MapAuthor;
    public List<Entity> Entities = new List<Entity>();
}

这是被序列化的类。在同一程序集中调用序列化时,内部变量是否可以视为公共变量呢?但是,代码在SavedMap.GetType()函数处抛出异常,我已经尝试过typeof(Map),但没有成功。我想这是因为我需要另一种处理每个新类(深度序列化)的方式,请问应该如何实现?

此外,我发现一些示例中没有接口继承或属性,因此我也没有添加它们,但我打算使用IXmlSerializable,不过我不知道如何在WriteXML实现中调用另一个序列化。


你能分享一下你尝试过的样例吗? - Fredrik Mörk
对不起,我之前没有做那件事。 - Johnny
“如果序列化是从同一程序集中调用的,那么内部成员可以被视为公共成员吗?” 不行。XmlSerializer仅考虑公共成员。(实际上,XmlSerializer创建一个动态程序集来保存执行序列化和反序列化的代码,因此实际序列化始终在与您的代码不同的程序集中进行。) - itowlson
谢谢你的提示。我已经将List更改为public,但现在我得到了“反射类型时出错”的异常。现在错误出现在实体类本身上? - Johnny
你说它抛出了异常,但是确切的最内层异常是什么?我猜测它没有识别到类型,如果是这样的话,请参考Josh已经提到的[XmlInclude] - Marc Gravell
顺便提一下,公共(甚至是内部)字段是一个很大的代码异味,并且请注意,[XmlSerializer] 仅作用于公共字段/属性(从不作用于内部)。 - Marc Gravell
4个回答

7
在你的类中添加Serializable和XmlInclude:
[System.Serializable]
[System.Xml.Serialization.XmlInclude(typeof(Entity))]
public class Map
{
    internal string MapName;
    internal string MapDescription;
    internal string MapAuthor;
    public List<Entity> Entities = new List<Entity>();
}

使用方法:

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Map));
serializer.Serialize(mapWriter, savedMap);

1
不错的回答!
[System.Serializable] [System.Xml.Serialization.XmlInclude(typeof(Entity))]
实际上,您在使用XmlSerializer时并不需要它。
另外请记住,“List<Entity> Entities”应该是List,而不是IList。
- Alexanderius

3
只要图形中的所有对象都是可序列化的,它将序列化整个对象图(对象及其通过公共成员递归公开的任何对象)。不同的串行器对于什么是可串行化的有不同的规则。例如,XmlSerializer需要一个默认的公共构造函数。
此外,XML序列化程序需要能够仅基于类型信息和这些类型上的属性告诉它将序列化哪种类型。因此,例如,您有一个具有Animal类型属性的类,但在运行时,XmlSerializer在其中找到了Dog类型的对象。为支持此操作,您需要使用XmlInclude属性,让它提前知道它需要支持Dog。
为了使对象图的某些部分不出现在序列化输出中,您将使用XmlIgnore属性。不同的串行器还具有包含/忽略等不同属性。
希望这有助于澄清一点。您可能还想阅读MSDN中的此主题

我能在类实现中添加[XmlIgnore]吗?由于List<Component>中的多态性,我不能忽略InputComponent对象! - Johnny
不,AttributeTargets 只允许你在字段(Field)、属性(Property)、参数(Parameter)或返回值(ReturnValue)上指定 XmlIgnore,尽管我甚至不确定它如何与后两者一起使用。 - Josh

2
关于Josh Einstein提到的类型问题,您不必使用XmlInclude属性:您还可以将类型列表传递给序列化程序(签名为XmlSerializer(Type baseType, Type[] extraTypes))。如果有可能额外类型列表会随着时间增长,应特别注意这一点。
找到额外类型可以通过反射对象进行序列化或在启动时反射已加载的程序集以获取任何所需类型来完成。
编辑:原始示例:
public abstract class Animal
{
}

public class Dog : Animal
{
}

public class Cat : Animal
{
}

public static class AnimalSerializer
{
    public static void Serialize(List<Animal> animals, Stream stream)
    {
        List<Type> animalTypes = new List<Type>();
        foreach (Animal animal in animals)
        {
            Type type = animal.GetType();
            if (!animalTypes.Contains(type))
            {
                animalTypes.Add(type);
            }
        }
        XmlSerializer serializer = new XmlSerializer(typeof(List<Animal>), animalTypes.ToArray());
        serializer.Serialize(stream, animals);
    }
}

这是我注意到并想问的另一件事情!你能分享一个例子吗? - Johnny

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接