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
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.