我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合。可是有时候我们使用反射动态加载程序集的时候会失败,因为除非我们手动将接口实现类的程序集放在项目生成后的bin目录下,或者是在GAC中,否者.Net Framework/.Net Core并不知道该到哪里去寻找接口实现类的dll程序集文件。幸运的是我们如果使用 AppDomain.CurrentDomain.AssemblyResolve事件和AppDomain.CurrentDomain.TypeResolve事件,就可以通过C#代码来自定义程序集加载逻辑,当C#反射解析程序集或类型失败的时候,通过执行自定义程序集加载逻辑来找到相应的程序集dll文件。

例如现在我们定义了一个普通的C#类库项目叫MessageDisplay,如下图所示

里面只包含了一个C#类MessageDisplayHelper.cs文件,MessageDisplayHelper.cs代码如下:

using System;

namespace MessageDisplay
{
public class MessageDisplayHelper
{
public string Display()
{
return "This is a message!";
}
}
}

然后我们定义一个C#控制台程序叫AssemblyResolverConsle:

在这个控制台程序中我们不直接引用MessageDisplay程序集,而是使用反射加载程序集MessageDisplay,然后使用反射动态构造MessageDisplayHelper类。由于我们没有在控制台程序AssemblyResolverConsle中直接引用MessageDisplay程序集,所以在调用Assembly.Load方法动态加载程序集的时候会失败,从而触发AppDomain.CurrentDomain.AssemblyResolve事件,而之后在调用Type.GetType("MessageDisplay.MessageDisplayHelper")时也会失败,又触发AppDomain.CurrentDomain.TypeResolve事件。

AssemblyResolverConsle控制台程序的Program.cs文件代码如下:

using System;
using System.Reflection; namespace AssemblyResolverConsle
{
class Program
{
static void Main(string[] args)
{
//当程序集(Assembly)通过反射加载失败的时候会触发AssemblyResolve事件,这里注册AssemblyResolve事件的处理函数为CurrentDomain_AssemblyResolve
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
//当类型(Type)通过反射加载失败的时候会触发TypeResolve事件,这里注册TypeResolve事件的处理函数为CurrentDomain_TypeResolve
AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve; //这里通过调用Assembly.Load方法反射加载MessageDisplay程序集会失败,因为本项目中没有引用该程序集,而且MessageDisplay程序集的dll文件也不在本项目生成的bin目录下,也不在GAC中。所以这里会触发AssemblyResolve事件,调用处理函数CurrentDomain_AssemblyResolve来尝试执行自定义程序集加载逻辑,然后处理函数CurrentDomain_AssemblyResolve会为这里的Assembly.Load方法返回MessageDisplay.dll程序集
var messageDisplayAssembly = Assembly.Load("MessageDisplay");
//使用反射动态调用MessageDisplayHelper类的构造函数
var messageDisplayHelper = messageDisplayAssembly.CreateInstance("MessageDisplay.MessageDisplayHelper");
Console.WriteLine(messageDisplayHelper.ToString()); //同样这里通过Type.GetType方法反射加载MessageDisplay程序集也会失败,会触发AssemblyResolve事件,调用处理函数CurrentDomain_AssemblyResolve来尝试执行自定义程序集加载逻辑,然后处理函数CurrentDomain_AssemblyResolve会为这里的Type.GetType方法返回所需要的程序集MessageDisplay.dll
//和Assembly.Load方法不同,如果AssemblyResolve事件的处理函数CurrentDomain_AssemblyResolve为Type.GetType方法返回了null,Type.GetType方法并不会抛出异常,而是也返回一个null
Type type = Type.GetType("MessageDisplay.MessageDisplayHelper, MessageDisplay");
Console.WriteLine(type.ToString()); //下面这里通过Type.GetType方法只反射类型MessageDisplay.MessageDisplayHelper,而不反射程序集MessageDisplay,所以会触发TypeResolve事件,调用处理函数CurrentDomain_TypeResolve来尝试执行自定义程序集加载逻辑,然后处理函数CurrentDomain_TypeResolve会为这里的Type.GetType方法返回所需要的程序集MessageDisplay.dll
//同样如果TypeResolve事件的处理函数CurrentDomain_TypeResolve为Type.GetType方法返回了null,Type.GetType方法并不会抛出异常,而是也返回一个null
type = Type.GetType("MessageDisplay.MessageDisplayHelper");
Console.WriteLine(type.ToString()); Console.WriteLine("Press any key to quit...");
Console.ReadLine();
} /// <summary>
/// TypeResolve事件的处理函数,该函数用来自定义程序集加载逻辑
/// </summary>
/// <param name="sender">事件引发源</param>
/// <param name="args">事件参数,从该参数中可以获取加载失败的类型的名称</param>
/// <returns></returns>
private static Assembly CurrentDomain_TypeResolve(object sender, ResolveEventArgs args)
{
//根据加载失败类型的名字找到其所属程序集并返回
if (args.Name.Split(",")[] == "MessageDisplay.MessageDisplayHelper")
{
//我们自定义的程序集加载逻辑知道MessageDisplay.MessageDisplayHelper类属于MessageDisplay程序集,而MessageDisplay程序集在C:\AssemblyResolverConsle\Reference\MessageDisplay.dll这个路径下,所以这里加载这个路径下的dll文件作为TypeResolve事件处理函数的返回值
return Assembly.LoadFile(@"C:\AssemblyResolverConsle\Reference\MessageDisplay.dll");
} //如果TypeResolve事件的处理函数返回null,说明TypeResolve事件的处理函数也不知道加载失败的类型属于哪个程序集
return null;
} /// <summary>
/// AssemblyResolve事件的处理函数,该函数用来自定义程序集加载逻辑
/// </summary>
/// <param name="sender">事件引发源</param>
/// <param name="args">事件参数,从该参数中可以获取加载失败的程序集的名称</param>
/// <returns></returns>
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
//根据加载失败程序集的名字找到该程序集并返回
if (args.Name.Split(",")[] == "MessageDisplay")
{
//我们自定义的程序集加载逻辑知道MessageDisplay程序集在C:\AssemblyResolverConsle\Reference\MessageDisplay.dll这个路径下,所以这里加载这个路径下的dll文件作为AssemblyResolve事件处理函数的返回值
return Assembly.LoadFile(@"C:\AssemblyResolverConsle\Reference\MessageDisplay.dll");
} //如果AssemblyResolve事件的处理函数返回null,说明AssemblyResolve事件的处理函数也无法找到加载失败的程序集,那么整个程序就会抛出异常报错
return null;
}
}
}

所以AppDomain.CurrentDomain.AssemblyResolve事件和AppDomain.CurrentDomain.TypeResolve事件,给反射加载程序集失败和加载类型失败提供了一个很好的解决途径,可以允许开发者自定义程序集解析逻辑。我们不再需要把一个.Net程序所需要用到的所有dll文件都要求放到bin目录下或GAC中,而是可以放在任何位置,通过AppDomain.CurrentDomain.AssemblyResolve事件和AppDomain.CurrentDomain.TypeResolve事件的处理函数来动态加载。

使用C#的AssemblyResolve事件动态解析加载失败的程序集的更多相关文章

  1. 使用C#的AssemblyResolve事件和TypeResolve事件动态解析加载失败的程序集

    我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合. ...

  2. PHP启动:动态库加载失败(PHP Warning: PHP Startup: Unable to load dynamic library '/usr/local/php7/lib/php/extensions/no-debug-non-zts-20151012/*.so')

    今天在linux上面智障一般搞了好久,本来想安装个swoole的,然后用  php -m 的命令想看下安装的PHP扩展库,发现有的扩展库有的可以出来,有的加载失败, 加载失败的错误类型: PHP Wa ...

  3. .net加载失败的程序集重新加载

    在.net程序中,程序集是Lazy加载的,只有在用的时候才会去加载,当程序集加载失败时,会触发AppDomain.AssemblyResolve的事件,在这个事件中,我们甚至还可以进行补救,从别得地方 ...

  4. JQuery Mobile - 处理图片加载失败!

    重点来了:一定要记住error事件不冒泡(如果要用js的方法替换默认出错图片,记得把img的alt属性去掉). 相关的知识点:jquery的ready方法.$("img").err ...

  5. 当h5页面图片加载失败后,给定一个默认图

    本文主要讨论页面中图片加载失败后替换默认图片的几种方式 重点来了:一定要记住error事件不冒泡. 相关的知识点:jquery的ready方法.$("img").error().i ...

  6. 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

    浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...

  7. C#.Net 如何动态加载与卸载程序集(.dll或者.exe)3---- 动态加载Assembly应用程序

    下载 supergraphfiles.exe 示例文件. 应用程序体系结构 在我专攻代码之前,我想谈谈我尝试做的事.您可能记得,SuperGraph 让您从函数列表中进行选择.我希望能够在具体的目录中 ...

  8. Vue 动态图片加载路径问题和解决方法

    最近在做一个树形结构的组件,使用了Vue和element UI中el-tree组件.因为树中每个节点都需要显示一个图标图片,并且需要根据后台传入的数据类型动态地显示,所以图片的路径需要动态地加载.下面 ...

  9. Viper解析&加载配置

    Viper解析&加载配置 1    Viper是什么 Viper是一个方便Go语言应用程序处理配置信息的库.它可以处理多种格式的配置.它支持的特性: 设置默认值 从JSON.TOML.YAML ...

随机推荐

  1. SpringMVC底层数据传输校验的方案(修改版)

    团队的项目正常运行了很久,但近期偶尔会出现BUG.目前观察到的有两种场景:一是大批量提交业务请求,二是生成批量导出文件.出错后,再执行一次就又正常了. 经过跟踪日志,发现是在Server之间进行jso ...

  2. jQuery模块化开发

    //定义了命名空间. var Itcast = {}; //定义第二级别的 命名空间. var Itcast.Model = {}; var Itcast.Model.UIJs = (function ...

  3. Java ArrayIndexOutOfBoundsException: Exception Hierarchy

  4. 高度-宽度关系,同一div、不同div高度与宽度关系控制函数

    //对象1的高度等于对象2的高度n倍,调用方法:Ht1DivideHt2('#div2','#div1',3)//div2的高度是div1高度的3倍function Ht1DivideHt2(obj1 ...

  5. 搜索引擎之全文搜索算法功能实现(基于Lucene)

    之前做去转盘网的时候,我已经公开了非全文搜索的代码,需要的朋友希望能够前去阅读我的博客.本文主要讨论如何进行全文搜索,由于本人花了很长时间设计了新作:观点,观点对全文搜索的要求还是很高的,所以我又花了 ...

  6. 解决CUICatalog: Invalid asset name supplied问题

    这个问题其实是老问题,产生原因就是因为在使用的时候 [UIImage imageNamed:]时,图片不存在或者传入的图片名为nil.

  7. LeetCode题目总结(二)

    我的代码在github上,https://github.com/WINTERFELLS/LeetCode-Answers 这里只提供个人的解题思路,不一定是最好的. Problems 21-40 合并 ...

  8. [js插件开发教程]定制一个手风琴插件(accordion)

    本文带来一个垂直方向的手风琴插件开发,可以定制的功能如下: contentClass : 'panel', //面板样式navClass : 'nav', //导航样式activeClass : 'a ...

  9. RobotFrame连接MySql数据库

    RobotFrame连接MySql数据库这类的教程网上并不多,就算有,也是很多坑.小编今天为大家提供一个靠谱的教程,但是具体的包需要大家自己下载.废话不多说,看疗效~~~ 1.pip install ...

  10. 《深入理解Java虚拟机:JVM高级属性与最佳实践》读书笔记(更新中)

    第一章:走进Java 概述 Java技术体系 Java发展史 Java虚拟机发展史 1996年 JDK1.0,出现Sun Classic VM HotSpot VM, 它是 Sun JDK 和 Ope ...