【问题标题】:Force binary deserialization to fail when type modified修改类型时强制二进制反序列化失败
【发布时间】:2015-04-21 11:24:22
【问题描述】:

我正在寻找一种非侵入式的方式来强制反序列化在以下情况下失败:

  • 类型未在强命名程序集中定义。
  • 使用了 BinaryFormatter。
  • 自序列化以来,类型已被修改(例如,已添加属性)。

以下是以失败的 NUnit 测试的形式对问题进行说明/重现。我正在寻找一种通用方法来完成此操作,而无需修改 Data 类,最好只在序列化和/或反序列化期间设置 BinaryFormatter。我也不想涉及序列化代理,因为这可能需要针对每种受影响类型的特定知识。

在 MSDN 文档中找不到任何对我有帮助的东西。

[Serializable]
public class Data
{
  public string S { get; set; }
}

public class DataSerializationTests
{
    /// <summary>
    /// This string contains a Base64 encoded serialized instance of the
    /// original version of the Data class with no members:
    /// [Serializable]
    /// public class Data
    /// { }
    /// </summary>
    private const string Base64EncodedEmptyDataVersion =
        "AAEAAAD/////AQAAAAAAAAAMAgAAAEtTc2MuU3Rvcm0uRGF0YS5UZXN0cywgV"+
        "mVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2"+
        "VuPW51bGwFAQAAABlTc2MuU3Rvcm0uRGF0YS5UZXN0cy5EYXRhAAAAAAIAAAAL";

    [Test]
    public void Deserialize_FromOriginalEmptyVersionFails()
    {
        var binaryFormatter = new BinaryFormatter();
        var memoryStream = new MemoryStream(Convert.FromBase64String(Base64EncodedEmptyDataVersion));

        memoryStream.Seek(0L, SeekOrigin.Begin);

        Assert.That(
            () => binaryFormatter.Deserialize(memoryStream),
            Throws.Exception
        );
    }
}

【问题讨论】:

    标签: c# serialization deserialization binary-deserialization


    【解决方案1】:

    我在这里推荐一种“Java”方式——在每个可序列化的类中声明 int 字段,例如private int _Serializable = 0;,并检查您的当前版本和序列化版本是否匹配;更改属性时手动增加。如果您坚持使用自动化方式,则必须存储大量元数据并检查当前元数据和持久化元数据是否匹配(对序列化数据的性能/大小造成额外负担)。

    这是自动描述符。基本上,您必须将TypeDescriptor 实例存储为二进制数据的一部分,并在检索时检查持久化的TypeDescriptor 是否对当前TypeDescriptor 的序列化(IsValidForSerialization)有效。

    var persistedDescriptor = ...;
    var currentDescriptor = Describe(typeof(Foo));
    bool isValid = persistedDescriptor.IsValidForSerialization(currentDescriptor);
    
    [Serializable]
    [DataContract]
    public class TypeDescriptor
    {
      [DataMember]
      public string TypeName { get; set; }
      [DataMember]
      public IList<FieldDescriptor> Fields { get; set; }
    
      public TypeDescriptor()
      {
        Fields = new List<FieldDescriptor>();
      }
    
      public bool IsValidForSerialization(TypeDescriptor currentType)
      {
        if (!string.Equals(TypeName, currentType.TypeName, StringComparison.Ordinal))
        {
          return false;
        }
        foreach(var field in Fields)
        {
          var mirrorField = currentType.Fields.FirstOrDefault(f => string.Equals(f.FieldName, field.FieldName, StringComparison.Ordinal));
          if (mirrorField == null)
          {
            return false;
          }
          if (!field.Type.IsValidForSerialization(mirrorField.Type))
          {
            return false;
          }
        }
        return true;
      }
    }
    
    [Serializable]
    [DataContract]
    public class FieldDescriptor
    {
      [DataMember]
      public TypeDescriptor Type { get; set; }
      [DataMember]
      public string FieldName { get; set; }
    }
    
    private static TypeDescriptor Describe(Type type, IDictionary<Type, TypeDescriptor> knownTypes)
    {
      if (knownTypes.ContainsKey(type))
      {
        return knownTypes[type];
      }
    
      var descriptor = new TypeDescriptor { TypeName = type.FullName, Fields = new List<FieldDescriptor>() };
      knownTypes.Add(type, descriptor);
      if (!type.IsPrimitive && type != typeof(string))
      {
        foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).OrderBy(f => f.Name))
        {
          var attributes = field.GetCustomAttributes(typeof(NonSerializedAttribute), false);
          if (attributes != null && attributes.Length > 0)
          {
            continue;
          }
    
          descriptor.Fields.Add(new FieldDescriptor { FieldName = field.Name, Type = Describe(field.FieldType, knownTypes) });
    
        }
      }
      return descriptor;
    }
    
    public static TypeDescriptor Describe(Type type)
    {
      return Describe(type, new Dictionary<Type, TypeDescriptor>());
    }    
    

    我还考虑了一些缩短持久元数据大小的机制——比如从 xml-serialized 或 json-serialized TypeDescriptor 计算 MD5;但在这种情况下,新属性/字段会将您的对象标记为不兼容序列化。

    【讨论】:

    • 这是一个可能的解决方案,也是非侵入性的,这正是我所要求的。我不会那样做,但它给了我一些思考的余地。然而,我有一种感觉,制作这样的产品准备好比实际修改序列化类需要更多的努力。我希望某种“秘密”标志能够将BinaryFormatter 置于更严格的模式中。但除非出现其他任何内容,否则我会将您的答案标记为正确。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 2017-08-22
    • 2015-07-26
    • 1970-01-01
    • 2014-01-04
    • 2015-07-13
    • 2023-01-14
    相关资源
    最近更新 更多