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__BackingFieldBinaryFormatter 在序列化有效负载中使用的成员的名称。 无法使用 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 方法。

理想情况下,可序列化类型的列表是封闭的,因为它意味着你知道哪些类型可以实例化,这有助于减少安全漏洞。