BinaryFormatter 功能参考
BinaryFormatter 是在 2002 年 .NET Framework 的初始版本中首次引入的。 若要了解如何替换 BinaryFormatter 的使用,它有助于了解 BinaryFormatter 的工作原理。
BinaryFormatter 可以序列化使用 [Serializable]
批注的或实现 ISerializable 接口的任何类型的任何实例。
成员名称
在最常见的方案中,类型使用 [Serializable]
进行批注,序列化程序使用反射来序列化所有字段(公共和非公共),使用 [NonSerialized]
批注的除外。 默认情况下,序列化的成员名称将与类型的字段名称匹配。 一直以来,这导致了不兼容,即便在 [Serializable]
类型上重命名了专用字段。 在迁移离开 BinaryFormatter 的过程中,有必要了解序列化字段名称的处理和重写方式。
C# 自动属性
对于 C# 自动实现的属性 ({ get; set; }
), BinaryFormatter 将序列化 C# 编译器生成的后盾字段,而不是属性。 这些序列化后盾字段的名称包含非法的 C# 字符,无法控制。 C# 反编译程序(例如 https://sharplab.io/ 或 ILSpy)可以演示如何向运行时显示 C# 自动属性。
[Serializable]
internal class PropertySample
{
public string Name { get; set; }
}
之前的类由 C# 编译器转换为:
[Serializable]
internal class PropertySample
{
private string <Name>k__BackingField;
public string Name
{
get
{
return <Name>k__BackingField;
}
set
{
<Name>k__BackingField = value;
}
}
}
在这种情况下,<Name>k__BackingField
是 BinaryFormatter
在序列化有效负载中使用的成员的名称。 无法使用 nameof
任何其他 C# 运算符来获取此名称。
ISerializable 接口附带了 GetObjectData 方法,允许用户使用其中一种 AddValue 方法来控制名称。
// Note lack of any special attribute.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", this.Name);
}
如果已应用此类自定义项,则还需要在反序列化期间提供该信息。 这可以通过使用serialization构造函数实现,其中所有值都从 SerializationInfo 读取,使用它提供的某个 Get
方法。
private PropertySample(SerializationInfo info, StreamingContext context)
{
this.Name = info.GetString("Name");
}
注意
此处特意未使用 nameof
运算符,因为有效负载可以持久化,而属性可以稍后重命名。 因此,即使它被重命名了(例如改为 FirstName
,因为你决定也要引入 LastName
属性),为了保持向后兼容,serialization 仍应使用本来可以在其他地方持久化的旧名称。
Serialization 绑定器
建议使用 SerializationBinder 来控制类加载并强制加载哪个类。 这样可以最大程度地减少安全漏洞(因此,即使攻击者修改有效负载以反序列化和加载其他内容,也只有受允许的类型会被加载)。
使用此类型需要从中继承并重写 BindToType 方法。
理想情况下,可序列化类型的列表是封闭的,因为它意味着你知道哪些类型可以实例化,这有助于减少安全漏洞。