BinaryFormatter not supporting Version Tolerance?

According the the .NET docs, Version Tolerance was added to BinaryFormatter in .NET 2.0:Successful Formatting In .NET Framework Versions 1.1 and 2.0 | Microsoft Learn

So in theory, I should be able to remove a field from a Serialized class, and not have things blow up. ie:

//Version 1
SaveData {
    public int Num1;
   public int Num2;
}

//Version 2
SaveData {
   public int Num1;
}

Even though I removed Num2, the older versions of the class should still deserialize ok, due to Version Tolerance: "In the case of removing an unused member variable, the binary formatter will simply ignore the additional information found in the stream. " Successful Formatting In .NET Framework Versions 1.1 and 2.0 | Microsoft Learn

Instead, Unity is throwing an Error: System.Runtime.Serialization.SerializationException: Field “Num2” not found in class SaveData

Is there a way to get this working in Unity?

1 Like

following

Since unity uses Mono, it’s that which needs to be considered when it comes to specific support like this.

And decompiling System.Runtime.Serialization.Formmater.Binary.ObjectReader shows that it doesn’t support this:

    private ObjectReader.TypeMetadata ReadTypeMetadata(BinaryReader reader, bool isRuntimeObject, bool hasTypeInfo)
    {
      ObjectReader.TypeMetadata typeMetadata = new ObjectReader.TypeMetadata();
      string str1 = reader.ReadString();
      int length1 = reader.ReadInt32();
      Type[] typeArray = new Type[length1];
      string[] strArray = new string[length1];
      for (int index = 0; index < length1; ++index)
        strArray[index] = reader.ReadString();
      if (hasTypeInfo)
      {
        TypeTag[] typeTagArray = new TypeTag[length1];
        for (int index = 0; index < length1; ++index)
          typeTagArray[index] = (TypeTag) reader.ReadByte();
        for (int index = 0; index < length1; ++index)
          typeArray[index] = this.ReadType(reader, typeTagArray[index]);
      }
      if (!isRuntimeObject)
      {
        long assemblyId = (long) reader.ReadUInt32();
        typeMetadata.Type = this.GetDeserializationType(assemblyId, str1);
      }
      else
        typeMetadata.Type = Type.GetType(str1, true);
      typeMetadata.MemberTypes = typeArray;
      typeMetadata.MemberNames = strArray;
      typeMetadata.FieldCount = strArray.Length;
      if (this._surrogateSelector != null)
      {
        ISurrogateSelector selector;
        ISerializationSurrogate surrogate = this._surrogateSelector.GetSurrogate(typeMetadata.Type, this._context, out selector);
        typeMetadata.NeedsSerializationInfo = surrogate != null;
      }
      if (!typeMetadata.NeedsSerializationInfo)
      {
        if (!typeMetadata.Type.IsSerializable)
          throw new SerializationException("Serializable objects must be marked with the Serializable attribute");
        typeMetadata.NeedsSerializationInfo = typeof (ISerializable).IsAssignableFrom(typeMetadata.Type);
        if (!typeMetadata.NeedsSerializationInfo)
        {
          typeMetadata.MemberInfos = new MemberInfo[length1];
          for (int index = 0; index < length1; ++index)
          {
            FieldInfo fieldInfo = (FieldInfo) null;
            string name1 = strArray[index];
            int length2 = name1.IndexOf('+');
            if (length2 != -1)
            {
              string str2 = strArray[index].Substring(0, length2);
              string name2 = strArray[index].Substring(length2 + 1);
              for (Type baseType = typeMetadata.Type.BaseType; baseType != null; baseType = baseType.BaseType)
              {
                if (baseType.Name == str2)
                {
                  fieldInfo = baseType.GetField(name2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                  break;
                }
              }
            }
            else
              fieldInfo = typeMetadata.Type.GetField(name1, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (fieldInfo == null)
              throw new SerializationException("Field \"" + strArray[index] + "\" not found in class " + typeMetadata.Type.FullName);
            typeMetadata.MemberInfos[index] = (MemberInfo) fieldInfo;
            if (!hasTypeInfo)
              typeArray[index] = fieldInfo.FieldType;
          }
          typeMetadata.MemberNames = (string[]) null;
        }
      }
      if (!this._typeMetadataCache.ContainsKey((object) typeMetadata.Type))
        this._typeMetadataCache[(object) typeMetadata.Type] = (object) typeMetadata;
      return typeMetadata;
    }

You could get around this by writing your own ISerializationSurrogate:

This is what I did to get binary serialization support with unity object support in my serialization engine I’ve been working on.