我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合。可是有时候我们使用反射动态加载程序集的时候会失败,因为除非我们手动将接口实现类的程序集放在项目生成后的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事件和TypeResolve事件动态解析加载失败的程序集的更多相关文章

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

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

  2. 深入理解DOM事件类型系列第六篇——加载事件

    前面的话 提到加载事件,可能想到了window.onload,但实际上,加载事件是一大类事件,本文将详细介绍加载事件 load load事件是最常用的一个事件,当页面完全加载后(包括所有图像.java ...

  3. JavaScript-onerror事件:图片加载失败后不显示

    HTML: <img src="http://www.mazey.net/images/upload/image/20170518/1495122198180663.gif" ...

  4. jQuery 滚动条 滚动到底部(下拉到底部) 加载数据(触发事件、处理逻辑)、分页加载数据

    1.针对浏览器整个窗口滚动 主要代码: <script type="text/javascript"> ; function GetProductListPageFun ...

  5. 使用事件捕获实时捕获img是否加载完毕, 实现iframe内容高度自动适应

    如何判断在html中图片加载完毕呢? 给img图片加onload事件呗. 如何判断一个界面中所有的图片加载完毕呢? 给所有的图片加上onload事件呗. 如果有1000张图片那要怎么绑定事件呢? 我们 ...

  6. 利用伪类选择器与better-scroll的on事件所完成的上拉加载

    之前给大家分享过一篇上拉加载 利用了better-scroll的pullUpDown 和DOM元素的删除添加  感觉那样不太好 今天给大家分享一个不同的上拉加载思想 代码如下 class List { ...

  7. setTimeout用于取消多次执行mouseover或者mouseenter事件,间接实现hover的悬停加载的效果.

    Mouseenter在鼠标滑上去不会对其子元素也发生监听, Mouseover在鼠标滑上去会对其子元素发生监听. 所以对于事件的监听,我们要看需求,这里是对父元素的监听,不需要对子元素做监听.就用mo ...

  8. 监听table滚动事件,滚动到底部时加载数据

    mounted() { this.$refs.scrollTable.addEventListener( 'scroll',(event) => { this.getDistance(event ...

  9. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    重温.NET下Assembly的加载过程   最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...

随机推荐

  1. svn 创建本地仓库

    1. svnadmin create ~/repository 2. svnserve -d -r ~/repository 3. svn checkout file://~/repository $ ...

  2. Windows Server 2008 R2 搭建网站详细教程

    转自:http://jingyan.baidu.com/album/642c9d34098bf5644a46f71f.html?picindex=4 网上都有一些Windows Server 2008 ...

  3. MobileWeb 适配总结

    开门见山,本篇将总结一下 MobileWeb 的适配方法,即我们常说的H5页面.手机页面.WAP页.webview页面等等. 本篇讨论的页面指专门针对手机设备设计的页面,并非兼容全设备的响应式布局. ...

  4. Python-并发编程(协程)

    今天说说协程 一.引子 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两 ...

  5. 类中调用界面ActiveX控件报错当前线程不在单线程单元中因此无法实例化 ActiveX 控件的解决办法

    解决办法是Form类中定义一个静态的ActiveX对象,在formload中将界面上的ActiveX对象赋值给新定义的对象,类中访问该静态对象即可. public static AxClientDri ...

  6. 软件项目技术点(7)——在canvas上绘制自定义图形

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 图形种类 目前我们软件可以绘制出来的形状有如下这几种,作为开发者我们一直想支持用户可以拖拽的类似word里面图形库,但目前还没有找到比 ...

  7. nodejs项目windows下开机自启动

    Nodejs项目开机自启动 1. 在需要自启动的项目中安装 node-windows 模块 npm install node-windows --save 2. 在项目根目录创建nw.js文件 代码截 ...

  8. CentOS 7运维管理笔记(9)----Apache 安全控制与认证

    Apache 提供了多种安全控制手段,包括设置Web访问控制.用户登陆密码认证及 .htaccess 文件等.通过这些技术手段,可以进一步提升Apache服务器的安全级别,减少服务器受攻击或数据被窃取 ...

  9. python SQLAchemy多外键关联

    关联同一张表的两个字段 Customer表有2个字段都关联了Address表 创建表结构 orm_many_fk.py 只创建表结构 from sqlalchemy import Integer, F ...

  10. mysql 修改数据库密码

    MYSQL5.7以下版本的数据库密码使用的是 mysql这个数据库里的user表的password这个字段, 修改密码只需: 1.update MySQL.user set password=pass ...