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事件的处理函数来动态加载。

  • 相关阅读:
    在TextBrowser显示中,如何让最新的数据永远出现在第一行或者是在窗口的最后显示信息
    在Qtlabel中显示数字十六进制和十进制都可以
    实现 在子界面的button按下,在主界面的label显示。
    今天在Qt子界面中的Button,转到槽转不过去,报错Qt The class containing 'Ui::MainWindow' could not be found in...
    Qt串口接收使用多个LCD控件显示不同的数据
    Qt图标自定义
    Qt绘制动态曲线
    3.3.2Qt的按钮部件
    Mesh Profile (5)启动配置(配网)
    SiliconLabs EFR32BG 定时器输入捕获和脉宽调制
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/8186148.html
Copyright © 2011-2022 走看看