.Net Framework中的AppDomain.AssemblyResolve事件的常见用法、问题,以及解决办法

一、简述
本文简要的介绍.NET Framework中System.AppDomain.AssemblyResolve事件的用法、使用注意事项,以及复杂场景下AssemblyResolve事件的污染问题和解决办法 。       System.AppDomain.AssemblyResolve事件可以理解为“找程序集事件“,假设程序集A依赖了程序集B、C,而B、C不在与A相同的目录下也不在常规的常规探测路径之内,此时相应AppDomain(一般指AppDomain.Current)的AssemblyResolve事件会被触发,程序集A中可尽早响应此事件,实现加载程序集B和C到对应的AppDomain 。可参考微软官方文档:Resolve assembly loads https://learn.microsoft.com/en-us/dotnet/standard/assembly/resolve-loads#how-the-assemblyresolve-event-works,该文比较详细的描述了AssemblyResolve的原理、用法和注意事项 。不过该文中虽多次提及注意事项,但给出的例程中并没有很好的体现注意事项,这是官方给马虎的小伙子埋下的坑之一 。
二、典型用法        场景1:让.NET软件的安装目录更整洁        将所有程序集都放到与主程序集(指.exe程序集也可能是一个.dll)相同的目录下,在软件规模稍大时显得杂乱无章 。即使认定用户不会查看软件的安装目录,对于爱干净的开发者来说也不能忍 。于是按功能模块建子文件夹,将相应的dll放入子文件夹内,通过在主程序集入口处立即响应AssemblyResolve事件已实现加载子文件夹中的程序集 。PS:主程序集是自己的exe文件时也可通过应用程序配置解决,如有兴趣可百度关键字”assemblyBinding“ 。               场景2:有序的组织软件内共享程序集        有的时候我们编写的软件是大型软件的插件,不巧的是,该大型软件每年升级版本,插件适配该宿主的多个版本 。插件软件有较多的程序集,其中少数依赖于宿主的API,多数与宿主无关能不妨称为软件内共享程序集,此时可将软件内共享程序集放安装目录,与宿主版本相关的程序集则放到安装目录下的子目录 。让宿主启动后加载对应子目录下的"主程序集",该程序集中尽快注册AssemblyResolve事件以加载软件内共享程序集 。       值得咱们这样追随的宿主平台,通常也有别的追求者,大家都用AssemblyResolve事件,林子大了,就可能出现本文主描述的AssemblyResolve污染问题 。               场景3:从字节数组加载程序集        从字节数组加载程序集…貌似可以做程序集加密!?不过不用期待太多特别是看完本文之后 。此处用一段摘自官方文档中的文字来描述:如果处理程序有权访问以字节数组形式存储的程序集的数据库,则它可以通过使用可采用字节数组的一种 Assembly.Load 方法重载来加载字节数组 。               更多用法,欢迎评论讨论 。
三、AssemblyResolve污染问题
       这里解释何为AssemblyResolve污染,以及问题起因 。       AssemblyResolve污染指AppDomain.AssemblyResolve事件的一个或多个响应函数没有按该事件的响应规范正确处理,影响所有依赖于该事件的功能模块运行稳定性 。通常问题现象是,运行到某个具体功能时提示找不到*.Resources.dll,或*.XmlSerializers .dll,或提未能从程序集xxx中加载类型yyy 。               咱们仅讨论无意中未按AssemblyResolve响应规范正确处理的情况 。此时的不规范响应通常是:遇到不能处理的程序集时应简单的返回null,但自.NET Framework 4.0开始,事件参数ResolveEventArgs增加了RequestingAssembly属性,该属性刚好是一个程序集且名字看着挺像是”正要找的程序集“,于是有些开发者在遇到不能处理的程序集时返回ResolveEventArgs.RequestingAssembly 。一旦返回了一个不为null的程序集对象,AssemblyResolve事件的其它响应函数变不会再被调用(本文在”探讨“一节中证实这个情况),从而引起本节描述的AssemblyResolve污染问题 。                       问题1:提未能从程序集xxx中加载类型yyy        如果有问题的响应函数先于咱们的程序响应了AppDomain.AssemblyResolve事件,则一直轮不到咱们程序集中的AppDomain.AssemblyResolve事件,于是出现这个问题 。               问题2:提示找不到*.Resources.dll,或*.XmlSerializers .dll        这类卫星程序集或附属程序集到上下文的加载,从.NET Framework 4.0开始也会触发事件,要求统一简单的返回null 。摘一段官网说明:        !Important        Beginning with the .NET Framework 4, the AssemblyResolve event is raised for satellite assemblies. This change affects an event handler that was written for an earlier version of the .NET Framework, if the handler tries to resolve all assembly load requests. Event handlers that ignore assemblies they do not recognize are not affected by this change: They return null, and normal fallback mechanisms are followed.

经验总结扩展阅读