在我向程序集添加强命名后 BinaryFormatter.Deserialize 出现 TargetInvocationException

TargetInvocationException on BinaryFormatter.Deserialize after i added strong naming to my assembly

我用 Save()Load() 方法编写了一个 DLL,使用 BinaryFormatter.Serialize()BinaryFormatter.Deserialize()List<MyObject> 保存或加载到我的文件中计算机。

在我决定为我的程序集添加强命名之前,这一直运行良好。一旦我在编译过程中添加了一个具有强名称的密钥,我的程序就不再能够加载预先强命名的文件。我得到一个 TargetInvocationException 和一个 FileLoadException 作为 InnerException 表示找不到 MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null

当我用十六进制编辑器打开文件时,我可以在文件中看到几个对我的程序集的引用:MyAssembly, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null

当我使用新的强命名程序集保存文件时,有一件事发生了变化:PublicKeyToken=123456789。当然,123456789 是另一个值。

现在我想到了几个问题:

  1. 为什么保存文件的 Assembly 版本很重要?
  2. 如果我想编写另一个程序来访问这些文件中的数据...这可能吗?
  3. 我可以转换预强命名文件以便我可以使用强命名程序集读取它们吗?

我已经尝试将 PublicKeyToken=null 更改为 PublicKeyToken=123456789,但这只是抛出一个 SerializationException 表示没有有效的 BinaryHeader 或对象版本已更改。

强命名程序集是 不同的 程序集(因为具有不同 public 键的程序集实际上是不同的程序集)。

鉴于此,您不能加载它们 而不是 预期的更合理,因为 BinaryFormatter 将默认尝试加载完全需要的程序集。

你可以做的是创建你的 SerializationBinder ,你用当前执行程序集的 PublicKey 替换 null PublicKey 。您的自定义序列化活页夹可用于分配 BinaryFormatter.Binder 属性.

var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };

其中 MyCustomBinder 是(只是相关部分):

sealed class MyCustomBinder : SerializationBinder
{
   public override Type BindToType(string assemblyName, string typeName)
   {
       var name = new AssemblyName(assemblyName);
       if (name.GetPublicKeyToken() == null) // Better check here...
       {
           var publicKeyToken = Assembly.GetExecutingAssembly()
               .GetName().GetPublicKeyToken();

           name.SetPublicKeyToken(publicKeyToken);
       }

       // Now let's create required type using name and typeName
   }

   // Other code
}

当然你应该只为你自己的程序集做这件事(而不是你不知道的任何其他类型)并且你应该仔细检查这不会破坏你的应用程序安全性。

编辑:请注意某些类型使用序列化代理项(例如 MemberInfoMemberInfoSerializationHolder)。由于此代理项而加载的类型将不会通过您的自定义序列化活页夹。值得注意的案例是代表。是的,序列化委托并不是什么好主意,我倾向于避免它(另见 .NET 4.5 MethodInfo serialization breaking change)但如果你不得不忍受它,你还需要处理 AppDomain.CurrentDomain.AssemblyResolve 事件(具有相同的逻辑如上所述):在反序列化之前附加一个处理程序,并在完成后将其删除;理想情况下,您可以在序列化活页夹中执行所有操作:

sealed class MyCustomBinder : SerializationBinder, IDisposable
{
    public MyCustomBinder()
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
    }

    void IDisposable.Dispose()
    {
        AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
    }

    // Your code here
}

像这样使用:

using (var binder = new MyCustomBinder())
{
    var formatter = new BinaryFormatter { Binder = new MyCustomBinder() };
}

注意:我没试过,但如果你需要使用 AppDomain.AssemblyResolve 那么你 可能 甚至不需要自定义活页夹,一切都可以在那里完成...