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+Deserialize,as成目标类型。 - 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)。 - 课上用「外层元素名 + 内层再用
XmlSerializer写int/string子树」的方式,演示怎样和ReadStartElement/ReadEndElement配对,保证读写出来的层级一致。 - 引用类型成员若为
null,序列化结果里往往 看不到对应节点;需要「占位」要在自己的WriteXml里显式写。
让 Dictionary 进 XML
- 不能改 BCL 里的
Dictionary,做法是 新建泛型类SerizlizerDictionary<TKey, TValue>:继承Dictionary<TKey, TValue>,再实现IXmlSerializable。 - WriteXml:对每个键值对依次
Serializekey、再Serializevalue,XML 里就是一段交替出现的<int>...</int><string>...</string>这类扁平序列。 - ReadXml:
Read()跳过字典外层根节点后,在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. 为什么 SaveData 用 data.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 时,ReadXml 与 WriteXml 要满足什么约定?
题目
自定义序列化时,怎样保证写出来的 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产出形状兼容。 - 若
TKey或TValue本身是复杂对象,子树会变长,循环里仍按「一次 key、一次 value」成对读取,需要保证写入顺序完全相同。 - 字典无序:
foreach写入顺序不稳定,若业务要求稳定 diff,要在写入前排序或改用有序结构。
答题示例
格式是键值对交替序列化,读的时候也必须成对 Deserialize。
泛型换成复杂类型时,要保证每种类型自己的 XML 形状不会破坏成对节奏。
参考文章
- 4.必备知识点-让Dictionary支持序列化反序列化
3. 把 XML 工具脚本打成 unitypackage 时,勾选「包括依赖项」会带来什么影响?
题目
导出包时勾选包括依赖项,和只勾单个脚本相比,工程上各有什么取舍?
深入解析
- 包括依赖项会自动把引用到的资源、脚本拉进列表,减少「导入后编译报错缺脚本」的概率,但也可能 带入无意依赖,包体变大。
- 实践上应在导出对话框里 逐项扫一眼 勾选树,确认没有课时示例、临时场景被勾选。
- 本课示意只导出
XmlDataMgr相关脚本,示例 Lesson 保持不勾选,体现「可复用模块与教学代码分离」。
答题示例
包括依赖项会把引用链上的资源一起打进包,省事但可能变大、带进多余东西。
导出前要检查列表,正式包一般只勾要交付的文件夹。
参考文章
- 7.生成资源包
4. TestClass99 里数组、List、自定义字典在 XML 里各是什么形状?
题目
用课上的 TestClass99 测存档时,array、list、dic 三类成员在生成的 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