首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >动态加载.Net程序集的二进制序列化

动态加载.Net程序集的二进制序列化
EN

Stack Overflow用户
提问于 2013-09-18 20:22:00
回答 3查看 1.7K关注 0票数 9

我将类的一个实例序列化为一个文件(使用BinaryFormatter)

之后,在另一个项目中,我想反序列化这个文件,但是它没有工作,因为我的新项目没有我以前的类的描述。.Deserialize()得到一个异常

代码语言:javascript
运行
复制
Unable to find assembly '*MyAssembly, Version=1.9.0.0, Culture=neutral, PublicKeyToken=null'.*".

但是,我有程序集的.DLL,其中包含了我想反序列化的旧类的描述。

我不想在项目中添加引用这个DLL (我希望能够反序列化任何类型的程序集.)

如何通知序列化程序/反序列化程序使用动态加载的程序集?

EN

回答 3

Stack Overflow用户

发布于 2019-12-10 21:19:24

假设您是通过Assembly.Load()Assembly.LoadFrom()加载程序集,然后按照这个答案 to https://stackoverflow.com/q/9162279/3744182 by 克里斯·肖恩中的解释,您可以在反序列化期间使用AppDomain.AssemblyResolve事件加载动态程序集。但是,出于安全原因,您将希望防止加载完全出乎意料的程序集。

一项可能的执行办法是采用以下措施:

代码语言:javascript
运行
复制
public class AssemblyResolver
{
    readonly string assemblyFullPath;
    readonly AssemblyName assemblyName;

    public AssemblyResolver(string assemblyName, string assemblyFullPath)
    {
        // You might want to validate here that assemblyPath really is an absolute not relative path.
        // See e.g. https://stackoverflow.com/questions/5565029/check-if-full-path-given
        this.assemblyFullPath = assemblyFullPath;
        this.assemblyName = new AssemblyName(assemblyName);
    }

    public ResolveEventHandler AssemblyResolve
    {
        get
        {
            return (o, a) =>
                {
                    var name = new AssemblyName(a.Name);
                    if (name.Name == assemblyName.Name) // Check only the name if you want to ignore version.  Otherwise you can just check string equality.
                        return Assembly.LoadFrom(assemblyFullPath);
                    return null;
                };
        }
    }
}

然后,在启动的某个地方,向AppDomain.CurrentDomain.AssemblyResolve添加一个适当的AppDomain.CurrentDomain.AssemblyResolve,例如:

代码语言:javascript
运行
复制
class Program
{
    const string assemblyFullPath = @"C:\Full-path-to-my-assembly\MyAssembly.dll";
    const string assemblyName = @"MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

    static Program()
    {
        AppDomain.CurrentDomain.AssemblyResolve += new AssemblyResolver(assemblyName, assemblyFullPath).AssemblyResolve;
    }

ResolveEventHandler检查请求的程序集是否具有动态程序集的名称,如果有,则从预期的完整路径加载当前版本。

另一种选择是编写自定义SerializationBinder并将其附加到BinaryFormatter.Binder。在BindToType (string assemblyName, string typeName)中,绑定程序需要检查属于动态程序集的类型,并将它们适当地绑定到它们。这里的诀窍是处理动态加载的类型嵌套在来自另一个程序集的泛型中的情况,例如List<MyClass>。在这种情况下,assemblyName将是List<T>而不是MyClass的程序集的名称。有关如何执行此操作的详细信息,请参阅

评论 @sgnsajgon问询中,我想知道为什么我不能像在项目中显式引用签名程序集时那样反序列化流--只有formatter.Deserialize(stream)而没有其他东西。

虽然我不知道微软员工在设计这些类(回到.Net 1.1中)时在想什么,但这可能是因为:

顺便提一下,https://stackoverflow.com/q/703073/3744182对使用BinaryFormatter可能遇到的其他问题提供了有用的概述。

票数 5
EN

Stack Overflow用户

发布于 2013-09-18 20:54:22

二进制序列化对于DLL来说不是胡说八道.它记录数据序列化时包含类型的确切程序集。并坚持在反序列化数据时找到确切的程序集。确保序列化数据与类型匹配的唯一方法,采用任何快捷方式只会确保在幸运时获得异常,而在不符合条件时则会得到垃圾数据。这种情况迟早会发生的几率是100%。

因此,您需要彻底放弃可以使用“动态加载程序集”并将其“反序列化任何类型的类”的想法,这是一种幻想。您可以旋转命运之轮,并将一个<bindingRedirect>放在app.exe.config文件中,以强制CLR使用不同的程序集版本。处理这些事故现在是你的责任了。许多程序员抓住了机会,很少有人在没有吸取新教训的情况下从经验中回来。我们必须这样做,以了解其后果。那就去吧。

票数 3
EN

Stack Overflow用户

发布于 2019-12-15 16:48:25

首先,有关二进制序列化的一些事实(如果您只对解决方案感兴趣,请跳过它们):

  • 二进制序列化的目标是按位复制对象。这通常涉及私有字段的序列化,这些字段可能在不同版本之间发生更改。如果反序列化总是在与序列化相同的过程中进行(典型的用例:深度克隆、撤消/重做等等),这不是问题。
  • 因此,如果反序列化可以在不同的环境中进行(包括不同平台、框架版本、程序集的不同版本,甚至是同一程序集的模糊版本),则不建议进行二进制序列化。如果您知道这些都适用于您的情况,那么考虑由公共成员使用基于文本的序列化,例如XML或JSON序列化。
  • 看来微软的开始放弃 BinaryFormatter。虽然它只在.NET 5中被删除/标记为过时(虽然可以作为一个包使用),但在.NET Core2/3中也有许多类型以前在.NET框架中是可序列化的,但在.NET核心中不再可以序列化(例如)。( TypeEncodingMemoryStreamResourceSet、代表等)。

如果您仍然确定要通过使用BinaryFormatter来解决这个问题,那么您有以下选项:

1.最简单的情况:只有程序集版本更改了

您可以向assemblyBinding文件中添加一个简单的app.config。只需将实际版本放在newVersion属性中即可。

代码语言:javascript
运行
复制
<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="MyAssembly" publicKeyToken="null" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

2.程序集名称和/或类型名称也已更改(或者如果您更喜欢编程解决方案)

IFormatter实现(因此也是BinaryFormatter)具有Binder属性。您可以使用它来控制程序集/类型名称解析:

代码语言:javascript
运行
复制
internal class MyBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        // mapping the known old type to the new one
        if (assemblyName.StartsWith("MyAssembly, ") && typeName == "MyNamespace.MyOldType")
            return typeof(MyNewType);

        // for any other type returning null to apply the default resolving logic:
        return null;
    }
}

用法:

代码语言:javascript
运行
复制
var formatter = new BinaryFormatter { Binder = new MyBinder() };
return (MyNewType)formatter.Deserialize(myStream);

如果您只需要一个程序集版本不敏感的解析器,您可以使用WeakAssemblySerializationBinder

3.新类型的内部结构也改变了

由于“任择议定书”没有涉及这一案件,我将不会对细节进行太深入的探讨。TL;DR:在这种情况下,您需要设置IFormatter.SurrogateSelector属性。如果类型名称和内部布局都发生了更改,则可以将其与Binder属性一起使用。如果您感兴趣,在备注部分的CustomSerializerSurrogateSelector类中有一些可能的子案例。

的最终想法:

  • 问题中的错误信息是一个提示,即使用BinaryFormatter可能不是实现目标的最佳选择。仅在确定要使用二进制序列化时才使用上述解决方案。否则,您可以尝试使用XML或JSON序列化,它基本上由公共成员序列化类型,并且不存储任何程序集信息。
  • 如果您想使用我上面链接的绑定/代理选择器,可以从NuGet下载这些库。它实际上还包含另一个二进制串行器 (免责声明:由我编写)。尽管它本机支持自定义类型的许多简单类型和集合(因此在序列化流中没有存储程序集标识),但您可能面临与问题中出现的问题相同的问题。
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18881659

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档