8.Xml实践项目总结

8.总结


8.1 核心要点速览

XmlSerializer 序列化

  • 流程就三步:有一个要存的对象 → new XmlSerializer(目标类型)Serialize 写到 StreamWriter,外层用 using 关掉流。
  • XmlSerializer 的泛型参数类型必须和实际传入的对象一致,否则序列化或反序列化阶段会直接报错。
说明
可见性 默认只会动 public 字段和属性;private / protected / internal 不会进 XML。
字典 原生 不支持 Dictionary<,>,需要自定义类型或换存储结构。
XmlElement("名字") 元素节点改名。
XmlAttribute / XmlAttribute("名字") 成员变成标签上的属性,并可改属性名。
XmlArray + XmlArrayItem 集合外层节点名、子项节点名一起定制;不配时也能序列化,只是节点名用默认规则。
自动属性 public int testPro { get; set; } 一样会序列化;未赋值时常见默认值会写出来。
string path = Application.persistentDataPath + "/Lesson1Test.xml";
using (StreamWriter streamWriter = new StreamWriter(path))
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(Lesson1Test));
    xmlSerializer.Serialize(streamWriter, lesson1Test);
}

反序列化

  • File.Exists,再 StreamReader + Deserializeas 成目标类型。
  • List 的坑:成员若在声明时就 new List 并塞了默认元素,反序列化时 不会先清空,会在原有集合后面 再追加 一份,看起来像「长度翻倍」。给会参与读档的类型做 List 时,尽量别在字段初始化里预填业务数据,或读前自己 Clear
if (File.Exists(path))
{
    using (StreamReader streamReader = new StreamReader(path))
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(Lesson1Test));
        Lesson1Test lesson1Test = xmlSerializer.Deserialize(streamReader) as Lesson1Test;
    }
}

IXmlSerializable

  • 三个方法:GetSchema 一般直接 return null写盘WriteXml(XmlWriter)读盘ReadXml(XmlReader)
  • 课上用「外层元素名 + 内层再用 XmlSerializerint / string 子树」的方式,演示怎样和 ReadStartElement / ReadEndElement 配对,保证读写出来的层级一致。
  • 引用类型成员若为 null,序列化结果里往往 看不到对应节点;需要「占位」要在自己的 WriteXml 里显式写。

让 Dictionary 进 XML

  • 不能改 BCL 里的 Dictionary,做法是 新建泛型类 SerizlizerDictionary<TKey, TValue>:继承 Dictionary<TKey, TValue>,再实现 IXmlSerializable
  • WriteXml:对每个键值对依次 Serialize key、再 Serialize value,XML 里就是一段交替出现的 <int>...</int><string>...</string> 这类扁平序列。
  • ReadXmlRead() 跳过字典外层根节点后,在 while成对 Deserialize 出 key 和 value,再 Add;结束前再 Read() 吃掉闭合,避免 reader 停在半道影响外层继续读。

XmlDataMgr 单例与 API 形状

需求侧就两件事:存(对象 + 逻辑文件名)取(目标类型 + 逻辑文件名)。实现上用饿汉单例,构造私有化,外部只走 Instance

方法 路径规则 要点
SaveData(object data, string fileName) persistentDataPath + "/" + fileName + ".xml" new XmlSerializer(data.GetType()),运行时按 真实类型 生成序列化器,多态场景不用手写一堆 if
LoadData(Type type, string fileName) 同上先试持久化目录 没有文件时改试 streamingAssetsPath;两处都没有则 Activator.CreateInstance(type) 返回默认对象,避免业务拿到 null 还要判空。
public void SaveData(object data, string fileName)
{
    string path = Application.persistentDataPath + "/" + fileName + ".xml";
    using (StreamWriter streamWriter = new StreamWriter(path))
    {
        XmlSerializer xmlSerializer = new XmlSerializer(data.GetType());
        xmlSerializer.Serialize(streamWriter, data);
    }
}

实现顺序建议

先打通 Lesson1Test 单文件存读 → 补上 IXmlSerializable 小类验证自定义节点 → 接入 SerizlizerDictionary → 最后把路径与异常边界收进 XmlDataMgr,用 TestClass99 一类聚合结构跑通数组、List、字典字段。


8.2 面试题精选

进阶题

1. XmlSerializer 默认序列化有哪些常见限制?

题目

Unity / .NET 里用 XmlSerializer 做存档时,哪些成员进不了 XML?Dictionary 为什么经常要自定义?

深入解析
  • 默认只处理 public 字段和可序列化的 public 属性;非 public 访问级别会被跳过。
  • Dictionary 没有开箱即用的 XML 映射,直接字段往往会编译或运行期失败,常见做法是 包装类型 + IXmlSerializable 或改成 List<KeyValuePair<,>> 等可序列化结构。
  • 序列化器类型必须与实际对象类型匹配;多态集合有时要考虑 XmlInclude 或更明确的类型声明。
答题示例

默认只序列化 public,字典原生不支持。
项目里一般用自定义类型实现 IXmlSerializable,或改成 List 等结构再存。

参考文章
  • 1.必备知识点-CSharp中XML序列化
  • 4.必备知识点-让Dictionary支持序列化反序列化

2. 反序列化后 List 元素变多是什么原因?怎么规避?

题目

同样的 XML 读两次,发现 List 里元素重复了一倍,可能是什么写法导致的?

深入解析
  • XmlSerializer 反序列化集合时,若字段在声明处已经 new 并填充了默认项,反序列化过程是 往现有集合追加,不会先清空。
  • 规避:字段声明为 null 或空集合,在业务代码里再初始化;或在反序列化后统一重置集合;测试存档时注意不要用带默认元素的字段做读档目标。
答题示例

字段声明时给 List 赋了默认值,反序列化会在原集合后面追加,看起来像翻倍。
读档类型里 List 不要预填业务数据,或读完后自己 Clear 再赋值。

参考文章
  • 2.必备知识点-CSharp中XML反序列化

3. 为什么 SaveDatadata.GetType(),而 LoadData 要单独传 Type

题目

封装 XML 管理器时,保存只给 object,读取却要 Type type,各自解决什么问题?

深入解析
  • 保存时手上有 运行时实例GetType() 能拿到真实派生类型,XmlSerializer 才能按完整形态写入多态字段。
  • 读取时只有文件名,内存里还没有对象,必须显式告诉反序列化器 要 new 出哪一种 CLR 类型,否则无法选择正确的 XmlSerializer 与根节点映射。
  • 这也是 LoadData 在文件不存在时仍能用 Activator.CreateInstance(type) 返回默认实例的前提。
答题示例

保存时有对象,GetType() 能拿到真实类型,序列化才正确。
读取时没有实例,必须传 Type 告诉反序列化器要还原成哪个类。

参考文章
  • 6.Xml数据管理类存储数据和读取数据

4. LoadData 先查持久化路径再查 StreamingAssets 适合什么数据?

题目

为何先 persistentDataPath,没有再试 streamingAssetsPath?各适合放什么内容?

深入解析
  • persistentDataPath 可写,适合 运行时生成或修改 的存档、配置覆写。
  • StreamingAssets 在包里只读,适合 随包发布的默认表、初始 XML;移动端等平台要注意异步或平台专用读取方式,本课示例用同步 StreamReader 演示逻辑顺序。
  • 两处都没有文件时返回默认实例,调用方要能区分「新档」与「读档失败」的业务语义。
答题示例

持久化路径可写,放玩家存档;StreamingAssets 放随包默认配置。
先写后读优先用玩家目录,没有再回退到包内默认文件。

参考文章
  • 6.Xml数据管理类存储数据和读取数据

深度题

1. 实现 IXmlSerializable 时,ReadXmlWriteXml 要满足什么约定?

题目

自定义序列化时,怎样保证写出来的 XML 一定能被自己的 ReadXml 读回来?

深入解析
  • 读写必须 对称:元素顺序、ReadStartElement / ReadEndElement 配对、内部是否再套一层 XmlSerializer 生成子树,都要一致。
  • XmlReader 的当前节点位置很敏感:跳过根节点、读完字典子节点后再 Read() 收尾等,都是为了和外层 XmlSerializer 控制的根元素衔接不冲突。
  • GetSchema 返回 null 在本课场景可行,但若要与 XSD 工具链集成则要另议。
答题示例

读写对称:节点层级、顺序、Read/Write 配对要和 WriteXml 一致。
Reader 位置要算准,多一层少一层都会导致后面反序列化错位。

参考文章
  • 3.必备知识点-IXmlSerializable接口
  • 4.必备知识点-让Dictionary支持序列化反序列化

2. SerizlizerDictionary 的 XML 布局有什么隐含假设?换键值类型时要注意什么?

题目

用交替 Serialize(key)Serialize(value) 存字典,反序列化端如何保证不读乱?

深入解析
  • 假设 XML 子节点严格 键、值、键、值 交替,且与 TKey/TValue 各自的 XmlSerializer 产出形状兼容。
  • TKeyTValue 本身是复杂对象,子树会变长,循环里仍按「一次 key、一次 value」成对读取,需要保证写入顺序完全相同。
  • 字典无序:foreach 写入顺序不稳定,若业务要求稳定 diff,要在写入前排序或改用有序结构。
答题示例

格式是键值对交替序列化,读的时候也必须成对 Deserialize。
泛型换成复杂类型时,要保证每种类型自己的 XML 形状不会破坏成对节奏。

参考文章
  • 4.必备知识点-让Dictionary支持序列化反序列化

3. 把 XML 工具脚本打成 unitypackage 时,勾选「包括依赖项」会带来什么影响?

题目

导出包时勾选包括依赖项,和只勾单个脚本相比,工程上各有什么取舍?

深入解析
  • 包括依赖项会自动把引用到的资源、脚本拉进列表,减少「导入后编译报错缺脚本」的概率,但也可能 带入无意依赖,包体变大。
  • 实践上应在导出对话框里 逐项扫一眼 勾选树,确认没有课时示例、临时场景被勾选。
  • 本课示意只导出 XmlDataMgr 相关脚本,示例 Lesson 保持不勾选,体现「可复用模块与教学代码分离」。
答题示例

包括依赖项会把引用链上的资源一起打进包,省事但可能变大、带进多余东西。
导出前要检查列表,正式包一般只勾要交付的文件夹。

参考文章
  • 7.生成资源包

4. TestClass99 里数组、List、自定义字典在 XML 里各是什么形状?

题目

用课上的 TestClass99 测存档时,arraylistdic 三类成员在生成的 XML 里分别怎么体现?和原生 List<int> 有何不同?

深入解析
  • TestItem99[] 会生成带多个子元素的外层节点,子项标签一般为类型名 TestItem99,每项下面再展开字段。
  • List<TestItem99> 同样是外层一个集合节点,内部若干 TestItem99 子节点,和数组在 XML 上的观感接近,区别主要在 CLR 侧类型。
  • SerizlizerDictionary<int, TestItem99> 走自定义 IXmlSerializable,子节点是 int 与 TestItem99 成对交替,不会出现标准 Dictionary 那种键值包装节点;读回时依赖课上实现的成对 Deserialize 顺序。
答题示例

数组和 List 都是外层一个块,里面多条子元素;字典课里是 int 和 TestItem99 交替出现。
这是因为字典用了自定义序列化,格式和 List 完全不一样,读的时候也要按成对规则解析。

参考文章
  • 6.Xml数据管理类存储数据和读取数据
  • 4.必备知识点-让Dictionary支持序列化反序列化

<|tool▁calls▁begin|><|tool▁call▁begin|>
Read


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏