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

自:https://www.cnblogs.com/OpenCoder/p/8186148.html

 

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

 

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

 

posted @ 2019-06-17 15:20  以函  阅读(439)  评论(0编辑  收藏  举报