c# - 使用 BinaryFormatter 序列化的对象导致对象版本更改的原因是什么?

标签 c# .net serialization version binaryformatter

根据 this question我有一个正在使用 BinaryFormatter 进行序列化的对象。由于各种原因,我们已经实现了像这样的穷人版本处理,在底部有一个 try-catch block ,用于新版本但旧版本中没有的字段:

private void readData(FileStream fs, SymmetricAlgorithm dataKey)
{
    CryptoStream cs = null;

    try
    {
        cs = new CryptoStream(fs, dataKey.CreateDecryptor(),
            CryptoStreamMode.Read);
        BinaryFormatter bf = new BinaryFormatter();

        string string1 = (string)bf.Deserialize(cs);
        // do stuff with string1

        bool bool1 = (bool)bf.Deserialize(cs);
        // do stuff with bool1

        ushort ushort1 = (ushort)bf.Deserialize(cs);
        // do stuff with ushort1

        // etc. etc. ...

        // this field was added later, so it may not be present
        // in the serialized binary data.  Check for it, and if
        // it's not there, do some default behavior

        NewStuffIncludedRecently newStuff = null;

        try
        {
            newStuff = (NewStuffIncludedRecently)bf.Deserialize(cs);
        }
        catch
        {
            newStuff = null;
        }

        _newStuff = newStuff != null ?
                new NewStuffIncludedRecently(newStuff) :
                new NewStuffIncludedRecently();
    }
    catch (Exception e)
    {
        // ...
    }
    finally
    {
        // ...
    }
}

我在我的机器上逐步检查了代码,这似乎有效。当我读取一个旧的序列化对象时,最里面的 try-catch 会按照我的要求处理丢失的部分。

当我去同事的机器上尝试读取对象的旧版本时,在顶部的第一个 Deserialize() 调用中抛出 SerializationException:

Binary stream '220' does not contain a valid BinaryHeader. Possible causes are invalid stream or object version change between serialization and deserialization.

因此我的问题是:是什么导致对象的版本发生变化?当我在对象的两个版本之间来回移动我的盒子时(注释/取消注释新字段)没有问题,但在另一个人的盒子上第一个 Deserialize() 炸弹。我什至不确定从哪里开始寻找,尽管我确实尝试过像这样使版本检查更宽松:

bf.AssemblyFormat =
    System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;

最佳答案

我还没有尝试使用 BinaryFormatter(我们使用 SoapFormatter),但我们解决这个问题的方法是实现类的手动序列化和反序列化,这样我们就可以跳过问题属性。

例如,我们的每个可序列化类都包含以下内容(请原谅 VB 代码;如果需要我可以转换):

' Serialization constructor
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
    ' This only needs to be called if this class inherits from a serializable class
    'Call MyBase.New(info, context)

    Call DeserializeObject(Me, GetType(ActionBase), info)
End Sub

' Ensure that the other class members that are not part of the NameObjectCollectionBase are serialized
Public Overridable Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext) Implements ISerializable.GetObjectData
    ' This only needs to be called if this class inherits from a serializable class
    'MyBase.GetObjectData(info, context)

    Call SerializeObject(Me, GetType(ActionBase), info)
End Sub

然后,所有类的序列化和反序列化以标准方法执行:

''' <summary>
''' This method is used to deserialize an object within the serialization constructor.
''' This is used when the caller does not want to explicitly and manually add every property to the 
''' SerializationInfo object
''' </summary>
''' <param name="theObject"></param>
''' <param name="theType"></param>
''' <param name="theInfo"></param>
''' <remarks></remarks>
Public Sub DeserializeObject(ByVal theObject As Object, ByVal theType As Type, ByVal theInfo As SerializationInfo)
    ' Exceptions are handled by the caller

    ' Manually deserialize these items
    With theType
        If theInfo.MemberCount > 0 Then
            For Each theField As FieldInfo In .GetFields(BindingFlags.DeclaredOnly Or BindingFlags.Instance Or BindingFlags.NonPublic)
                Try
                    ' Don't deserialize items that are marked as non-serialized
                    If Not theField.IsNotSerialized Then
                        If theField.FieldType.IsEnum Then
                            Try
                                theField.SetValue(theObject, System.Enum.Parse(theField.FieldType, CStr(theInfo.GetValue(theField.Name, theField.FieldType))))
                            Catch
                                theField.SetValue(theObject, TypeDescriptor.GetConverter(theField.FieldType).ConvertFrom(theInfo.GetInt32(theField.Name)))
                            End Try
                        Else
                            theField.SetValue(theObject, theInfo.GetValue(theField.Name, theField.FieldType))
                        End If
                    End If
                Catch
                End Try
            Next
        End If
    End With
End Sub

''' <summary>
''' This method is used to serialize an object within the GetObjectData serialization method.
''' This is used when the caller does not want to explicitly and manually add every property to the 
''' SerializationInfo object
''' </summary>
''' <param name="theObject"></param>
''' <param name="theType"></param>
''' <param name="theInfo"></param>
''' <remarks></remarks>
Public Sub SerializeObject(ByVal theObject As Object, ByVal theType As Type, ByVal theInfo As SerializationInfo)
    ' Exceptions are handled by the caller

    ' Manually serialize these items
    With theType
        For Each theField As FieldInfo In .GetFields(BindingFlags.DeclaredOnly Or BindingFlags.Instance Or BindingFlags.NonPublic)
            ' Don't serialize items that are marked as non-serialized
            If Not theField.IsNotSerialized Then
                theInfo.AddValue(theField.Name, theField.GetValue(theObject))
            End If
        Next
    End With
End Sub

关于c# - 使用 BinaryFormatter 序列化的对象导致对象版本更改的原因是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8393538/

相关文章:

c# - 使用 Linq 动态查询数据集

javascript - 抑制 .net 的 updatepanel 提交事件处理程序

c# - EXISTS 不引入子查询时,select 列表中只能指定一个表达式

c# UWP 窗口在屏幕上的位置问题

c# - 让 .net DLL 作为 Windows 服务运行的简单方法

c# - 从方法体内获取表达式树

c# - 为什么事件处理程序的返回类型总是 void?

php - FOSElastica + JMs 序列化程序格式错误的数据

json - 如何在 Jackson 中编写自定义序列化器和反序列化器?

c# - 如何序列化运行时将 "properties"添加到 Json