Принудительно выполнить двоичную десериализацию при изменении типа

Я ищу ненавязчивый способ принудительного сбоя десериализации при следующих обстоятельствах:

  • Тип не определен в сборке со строгим именем.
  • 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
        );
    }
}

1 ответ

Решение

Я бы порекомендовал "Java" способ здесь - объявить поле int в каждом сериализуемом классе, как private int _Serializable = 0; и проверьте, что ваша текущая версия и серийная версия совпадают; вручную увеличивать при изменении свойств. Если вы настаиваете на автоматическом способе, вам придется хранить много метаданных и проверять, соответствуют ли текущие метаданные и сохраненные метаданные (дополнительная нагрузка на производительность / размер сериализованных данных).

Вот автоматический дескриптор. В основном вам придется хранить TypeDescriptor экземпляр как часть ваших двоичных данных и при извлечении проверить, если сохраняются TypeDescriptor действительно для сериализации (IsValidForSerialization) против течения TypeDescriptor,

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>());
}    

Я также подумал о некотором механизме сокращения размера сохраняемых метаданных, например, о вычислении MD5 из XML-сериализованного или json-сериализованного TypeDescriptor; но в этом случае новое свойство / поле пометит ваш объект как несовместимый для сериализации.

Другие вопросы по тегам