我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合。可是有时候我们使用反射动态加载程序集的时候会失败,因为除非我们手动将接口实现类的程序集放在项目生成后的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. hdu 1565 方格取数(1) 状态压缩dp

    方格取数(1) Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  2. Linux-学习笔记(PHP向)<一>

    Linux常用命令 使用PHP服务器端脚本编程语言进行网站开发,需要在lamp环境下进行,Linux作为”四剑客”之一是有必要了解熟悉的,而Linux系统并不像windows操作系统那样,以图形化的界 ...

  3. window下Jekyll建站过程

    > 前言 最近决定要写一个博客,先后注册了博客园和CSND的博客,但是他们的界面主题都不是很符合自己的要求,还没有足够个性化的发挥空间,遂决定自己建一个博客. 网上找了一下教程,感觉都不太详细, ...

  4. puppeteer自动化测试

    1.基础知识 puppeteer.launch() 创建浏览器实例 puppeteer.newPage() 创建一个新页面 puppeteer.goto() 进入指定网站 page.screensho ...

  5. C语言--清理getchar缓存

    getchar()采用了缓冲区,而getch()才是立即获取,所以要想再用getchar()获取正确的值必须先清空缓冲区,如果是windows操作系统,用fflush(stdin)函数或rewind( ...

  6. css模仿百度首页

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 【Udacity】数据的集中程度:众数、平均数和中位数

    重视Code Review 极致--目标是成为优秀的开发者 Data tells a story!(数据会讲故事) 分析过程对于建模非常的重要,可以帮助我们减少实际上不相关的特征被错误的加入到模型中, ...

  8. eclipse 出现 jar包找不到 问题记录

    同事在下载maven私服项目的时候,自动更新失败.maven 一直提示 parent 更新失败但是其他的项目都是正常的,这就奇怪了. 最后 仔细查询后,发现是  同事在下载项目时候,项目是分clien ...

  9. data-* 自定义数据属性 遇到的坑

    除非data-*自定义数据属性的值是固定不变的,否则最好不要把data-*作为查询条件. 例子: <div data-index="0">hello</div&g ...

  10. java学习笔记之Java的特点

    Java在如今的发展趋势而言,一直都是处于流行的原因自然也是随之而存在的. java的特点如下几个方面: 1.简单性 Java 实际上是一个 C++去掉了复杂性之后的简化版.如果读者没有编程经验,会发 ...