【问题标题】:Deserialization problem: Error when deserializing from a different program version反序列化问题:从不同的程序版本反序列化时出错
【发布时间】:2011-06-16 15:29:09
【问题描述】:

在花了几个小时在 Internet 上搜索解决方案并尝试了一些解决方案后,我终于决定自己发布我的问题。

[问题背景]

我正在开发一个应用程序,它将分两部分进行部署:

  • 一个 XML Importer 工具:它的作用是加载/读取一个 xml 文件以填充一些数据结构,然后将其序列化为二进制文件。
  • 最终用户应用程序:它将加载由 XML 导入器生成的二进制文件,并对恢复的数据结构做一些事情。

目前,我仅将 XML 导入器用于这两个目的(这意味着我首先加载 xml 并将其保存到二进制文件,然后我重新打开 XML 导入器并加载我的二进制文件)。

[实际问题]

这工作得很好,我能够在 XML 加载后恢复我拥有的所有数据,只要我使用我的 XML Importer 的相同版本来做到这一点。这是不可行的,因为我至少需要两种不同的构建,一种用于 XML 导入器,另一种用于最终用户应用程序。请注意,我用于测试的两个版本的 XML Importer 在源代码和数据结构方面完全相同,唯一的区别在于构建号(为了强制不同的构建,我只需在某处添加一个空间并重新构建)。

所以我想做的是:

  • 构建我的 XML 导入器版本
  • 打开 XML 导入器,加载 XML 文件并将生成的数据结构保存到二进制文件中
  • 重建 XML 导入器
  • 打开新建的 XML Importer,加载之前创建的二进制文件并恢复我的数据结构。

此时,我得到一个异常:

SerializationException: Could not find type 'System.Collections.Generic.List`1[[Grid, 74b7fa2fcc11e47f8bc966e9110610a6, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadType (System.IO.BinaryReader reader, TypeTag code)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadTypeMetadata (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectInstance (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)

为了您的信息(不知道是否有用),它正在努力反序列化的实际类型是一个列表,网格是一个自定义类(它是正确可序列化的,因为我在使用相同版本的 XML 导入器)。

[潜在解决方案]

我确实相信它来自大会周围的某个地方,因为我阅读了许多关于此的帖子和文章。但是,我已经有一个自定义 Binder 来处理程序集名称的差异,如下所示:

public sealed class VersionDeserializationBinder : SerializationBinder
{ 
    public override Type BindToType( string assemblyName, string typeName )
    { 
        if ( !string.IsNullOrEmpty( assemblyName ) && !string.IsNullOrEmpty( typeName ) )
        { 
            Type typeToDeserialize = null; 
            assemblyName = Assembly.GetExecutingAssembly().FullName; 
            // The following line of code returns the type. 
            typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) ); 

            return typeToDeserialize; 
        } 

        return null; 
    }
}

我在这里反序列化之前分配给 BinaryFormatter:

    public static SaveData Load (string filePath) 
    {
        SaveData data = null;//new SaveData ();
        Stream stream;

        stream = File.Open(filePath, FileMode.Open);


        BinaryFormatter bformatter = new BinaryFormatter();
        bformatter.Binder = new VersionDeserializationBinder(); 
        data = (SaveData)bformatter.Deserialize(stream);
        stream.Close();

        Debug.Log("Binary version loaded from " + filePath);

        return data; 
    }

你们中有人知道我该如何解决它吗?太棒了,请漂亮:)

【问题讨论】:

  • 您是否在使用强命名程序集?这可能是它无法识别类相同的原因。
  • 说实话,我不知道关于装配的第一件事,今天是我第一次不得不绕过这些。我已经看到有很多关于这些的 msdn 页面,尽管我没有时间阅读它们,因为我在下午晚些时候偶然发现了它们。因此,要回答您的问题,我没有修改任何有关程序集的内容,也没有设置任何特殊内容;我不知道默认情况下程序集是否是强命名的(在这种情况下我使用这些)或不是(在这种情况下我不应该使用强命名的程序集)。

标签: exception types versioning deserialization .net-assembly


【解决方案1】:

将工作位移动到单独的程序集中,并在“服务器”和“客户端”中使用该程序集。根据您对问题的解释,如果这是核心问题,这应该可以解决“错误版本”问题。我还会将任何“模型”(即像网格这样的状态位)带到域模型项目中,并在两个地方都使用它。

【讨论】:

  • "将工作位移动到单独的程序集中并在“服务器”和“客户端”中使用该程序集" => 我怎样才能做到这一点?正如我在上面对 antlersoft 的回复评论中指出的那样,此时我完全不知道如何处理程序集,并且对它的用途只有一个非常模糊的想法。明天我回来工作时,我肯定会读到这一点,但如果你有一个起点,那就太棒了。
  • 所以,在下面的 antlersoft 解释之后,我意识到这确实是解决方案。不幸的是,当我使用 Unity 时,它并没有为我完成这项工作,并且它使使用库变得更加困难,特别是因为目标应用程序将是跨平台的。我感谢你们的帮助,并将在不使用我想要的工具的情况下以另一种方式做我想做的事:/
【解决方案2】:

当我遇到同样的问题时,我刚刚碰到了你的帖子。尤其是您使用 SerializationBinder 的代码示例对我帮助很大。我只需要稍微修改一下,就可以区分我自己的程序集和 Microsoft 的程序集。希望它也能对您有所帮助:

sealed class VersionDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;
        string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;

        //my modification
        string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
        if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;

        typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
        return typeToDeserialize;
    }
}

【讨论】:

    【解决方案3】:

    我相信问题在于您告诉它在执行程序集中查找 List,而实际上它在 System 程序集中。仅当原始程序集是您的程序集之一时,您才应在活页夹中重新分配程序集名称。

    此外,您可能必须在绑定器中专门处理泛型的参数类型,方法是解析类型名称并确保在返回参数化泛型类型时参数类型不是特定于外部程序集。

    【讨论】:

    • 关于第一部分,我明白为什么它会不稳定,尽管我不认为这里是这种情况,因为我也尝试注释掉重新分配的位并且仍然得到完全相同输出。现在关于第二部分......我不认为我得到它的一半:/我对此感到非常抱歉,尽管我昨天才开始使用序列化并且仍然是该领域的纯 N00B......(介于这个和程序集,看起来我“不想”理解你的答案>_
    • 程序集是 .Net 可执行文件,可以是 .exe 或 .dll。每个类都属于一个程序集。如果一个类在不同的程序集中,.Net 会倾向于认为它们是不同的类,即使源相同。上面的解决方案建议将您的公共类(您序列化的类)放入 .dll 程序集中(这将是 Visual Studio 中的一个单独项目),并让您的 .exe 引用该程序集。然后在反序列化时不会对类标识产生任何混淆(你根本不需要你的活页夹)。它使部署稍微复杂一些,你需要两者
    • 感谢您的澄清。非常简洁明了。现在是痛苦的部分:我不是在 Visual Studio 上工作,这会像你建议的那样让它变得更简单,而是在 Unity 3 上工作(游戏引擎 + 中间件,以防你不知道)。现在我不确定它如何处理 C# 程序集,也不确定它如何在 Unity 中创建依赖项,尽管我很确定这是可能的。我真正害怕的唯一一点是我的应用程序将是 OSX、iOS 和 Android 的跨平台(这是使用 Unity 的好处),这可能会出现问题,具体取决于处理依赖项的方式..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 2011-04-05
    相关资源
    最近更新 更多