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

    路由末端带有“/” app.route('/test/') 在浏览器中输入http://127.0.0.1:5000/test/ 和http://127.0.0.1:5000/test都能访问 路由末 ...

  2. C Primer Plus note3

    上面的源代码为什么输入q的时候,程序就会退出呢? while(条件语句),status == 1是条件语句,status只要是非0值的时候,status == 1的条件就满足,程序就会运行while循 ...

  3. Spring课程 Spring入门篇 6-3 ProxyFactoryBean及相关内容(下)

    1 解析 1.1 使用global advisors demo 1.2 jdk代理和cglib代理的选择 1.3 如何强制使用CGLIB实现AOP? 1.4 JDK动态代理和CGLIB字节码生成的区别 ...

  4. java 中国网建实现发送短信验证码

    现在中国网建上注册一个自己的账户, 然后里面有代码案例,也有相应的下载jar包的地址 package com.direct.note; import java.io.IOException; impo ...

  5. html+css 布局篇

    float 做了float后有一些不好的影响. 1.背景不能显示 由于浮动产生,如果对父级设置了(CSS background背景)CSS背景颜色或CSS背景图片,而父级不能被撑开,所以导致CSS背景 ...

  6. Mybatis学习随笔

    学习Mybatis路径(适合有java基础和mysql基础的小伙伴) 1.把项目搭建起来,跑一跑感受一下 2.测试基本映射 3.测试高级映射 4.测试动态sql 5.学习懒加载与缓存 6.与sprin ...

  7. base64编码 的 图片 另存为下载

    功能描述: 有一段base64字符串的图片,将其保存下载为png图片! 可以:  直接 a 链接下载:  <a id="tttt" download="1.jpg& ...

  8. Spring3实战第二章第二小节 IOC依赖注入 list和map集合

    Spring有多种依赖注入的形式,本篇文章仅介绍Spring通过xml进行IOC配置的方式. 1.Set注入 2.构造器注入 平常的Java开发中,程序员在某个类中需要依赖其它类的方法. 通常是new ...

  9. ns2.35-classifier.cc

    line143:recv() /* -*- Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */ /* * Copyri ...

  10. Linux文件系统的实现 ZZ

    作者:Vamei 出处:http://www.cnblogs.com/vamei Linux文件管理从用户的层面介绍了Linux管理文件的方式.Linux有一个树状结构来组织文件.树的顶端为根目录(/ ...