在C#的XML序列化场景中,当我们需要序列化一个基类类型的属性,但该属性实际存储的是派生类的实例时,默认的XmlSerializer无法识别派生类的结构,会导致序列化失败或者丢失派生类的特有数据。这时候就需要用到Known Types(已知类型)特性,告诉XmlSerializer需要额外识别哪些类型。

Known Types的核心作用
XmlSerializer在序列化对象时,只会处理序列化目标类型及其直接关联的类型,对于通过多态赋值进来的派生类,默认不会识别。Known Types的作用就是显式声明XmlSerializer需要额外支持的类型列表,让序列化器能够正确处理这些派生类的序列化和反序列化操作。
配置Known Types的两种方式
1. 使用XmlInclude特性标注基类
这是最常用的方式,直接在基类上通过XmlInclude特性声明需要支持的派生类型,XmlSerializer在序列化基类相关对象时会自动识别这些类型。
示例代码如下:
using System;
using System.IO;
using System.Xml.Serialization;
// 基类
[XmlInclude(typeof(Dog))]
[XmlInclude(typeof(Cat))]
public class Animal
{
public string Name { get; set; }
}
// 派生类Dog
public class Dog : Animal
{
public string BarkSound { get; set; }
}
// 派生类Cat
public class Cat : Animal
{
public string MeowSound { get; set; }
}
// 包含基类属性的容器类
public class AnimalShelter
{
public Animal Resident { get; set; }
}
class Program
{
static void Main()
{
// 创建包含派生类实例的容器对象
AnimalShelter shelter = new AnimalShelter
{
Resident = new Dog
{
Name = "小黑",
BarkSound = "汪汪汪"
}
};
// 序列化操作
XmlSerializer serializer = new XmlSerializer(typeof(AnimalShelter));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, shelter);
string xml = writer.ToString();
Console.WriteLine("序列化结果:");
Console.WriteLine(xml);
// 反序列化操作
using (StringReader reader = new StringReader(xml))
{
AnimalShelter deserializedShelter = (AnimalShelter)serializer.Deserialize(reader);
if (deserializedShelter.Resident is Dog dog)
{
Console.WriteLine($"反序列化结果:名字{dog.Name},叫声{dog.BarkSound}");
}
}
}
}
}
2. 构造XmlSerializer时传入已知类型列表
如果不想在基类上添加特性,也可以在创建XmlSerializer实例时,通过构造函数传入需要支持的已知类型数组,这种方式更加灵活,适合动态指定类型的场景。
示例代码如下:
using System;
using System.IO;
using System.Xml.Serialization;
// 基类不添加XmlInclude特性
public class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
public string BarkSound { get; set; }
}
public class Cat : Animal
{
public string MeowSound { get; set; }
}
public class AnimalShelter
{
public Animal Resident { get; set; }
}
class Program
{
static void Main()
{
AnimalShelter shelter = new AnimalShelter
{
Resident = new Cat
{
Name = "小白",
MeowSound = "喵喵喵"
}
};
// 构造时传入已知类型列表
Type[] knownTypes = new Type[] { typeof(Dog), typeof(Cat) };
XmlSerializer serializer = new XmlSerializer(typeof(AnimalShelter), knownTypes);
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, shelter);
string xml = writer.ToString();
Console.WriteLine("序列化结果:");
Console.WriteLine(xml);
using (StringReader reader = new StringReader(xml))
{
AnimalShelter deserializedShelter = (AnimalShelter)serializer.Deserialize(reader);
if (deserializedShelter.Resident is Cat cat)
{
Console.WriteLine($"反序列化结果:名字{cat.Name},叫声{cat.MeowSound}");
}
}
}
}
}
两种方式的适用场景对比
我们可以通过下表对比两种配置方式的特点,根据实际需求选择:
| 配置方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| XmlInclude特性标注 | 配置简单,一次标注全局生效,不需要每次构造序列化器时重复传入 | 需要修改基类代码,不够灵活,新增派生类需要修改基类特性 | 已知所有派生类,且基类代码可修改的场景 |
| 构造时传入类型列表 | 不需要修改基类代码,可动态指定类型,灵活性高 | 每次创建XmlSerializer都需要传入类型列表,容易遗漏 | 基类代码不可修改,或需要动态支持不同派生类的场景 |
注意事项
- 已知类型需要是基类可访问的派生类,否则XmlSerializer无法加载对应类型。
- 如果派生类还有自己的派生类,也需要一并加入到已知类型列表中,否则下一级派生类还是无法被识别。
- 使用XmlInclude特性时,如果多个地方都用到了该基类,只需要标注一次即可,不需要重复标注。
XmlSerializerKnown_TypesC#XML序列化反序列化修改时间:2026-07-10 23:45:33