zoukankan      html  css  js  c++  java
  • 使用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:AssemblyResolverConsleReferenceMessageDisplay.dll这个路径下,所以这里加载这个路径下的dll文件作为TypeResolve事件处理函数的返回值
                    return Assembly.LoadFile(@"C:AssemblyResolverConsleReferenceMessageDisplay.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:AssemblyResolverConsleReferenceMessageDisplay.dll这个路径下,所以这里加载这个路径下的dll文件作为AssemblyResolve事件处理函数的返回值
                    return Assembly.LoadFile(@"C:AssemblyResolverConsleReferenceMessageDisplay.dll");
                }
    
                //如果AssemblyResolve事件的处理函数返回null,说明AssemblyResolve事件的处理函数也无法找到加载失败的程序集,那么整个程序就会抛出异常报错
                return null;
            }
        }
    }

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

  • 相关阅读:
    《maven实战》笔记(5)----maven的版本
    《maven实战》笔记(4)----maven的仓库
    《maven实战》笔记(2)----一个简单maven项目的搭建,测试和打包
    《maven实战》笔记(1)----maven的初识
    web项目jsp出现The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path错误
    如何在国内使用google
    js实现仿购物车加减效果
    关于行内元素之间有空隙的问题,例如span与input之间
    jquery遍历DOM方法总结
    左侧手风琴下拉菜单导航(网页常用功能)
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/8186148.html
Copyright © 2011-2022 走看看