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

2021-07-16 15:17

阅读:694

我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合。可是有时候我们使用反射动态加载程序集的时候会失败,因为除非我们手动将接口实现类的程序集放在项目生成后的bin目录下,或者是在GAC中,否者.Net Framework并不知道该到哪里去寻找接口实现类的dll程序集文件。幸运的是我们如果使用 AppDomain.CurrentDomain.AssemblyResolve事件,就可以通过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事件。AssemblyResolverConsle控制台程序的Program.cs文件代码如下:

using System;
using System.Reflection;

namespace AssemblyResolverConsle
{
    class Program
    {
        static void Main(string[] args)
        {
            //当程序集通过反射加载失败的时候会触发AssemblyResolve事件,这里注册AssemblyResolve事件的处理函数为CurrentDomain_AssemblyResolve
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            //这里通过调用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());
            Console.ReadLine();
        }

        /// 
        /// AssemblyResolve事件的处理函数,该函数用来自定义程序集加载逻辑
        /// 
        /// 事件引发源
        /// 事件参数,从该参数中可以获取加载失败的程序集的名称
        /// 
        private static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            //根据加载失败程序集的名字找到该程序集并返回
            if (args.Name == "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这个事件,给反射加载程序集失败提供了一个很好的解决途径,可以允许开发者自定义程序集解析逻辑。我们不再需要把一个.Net程序所需要用到的所有dll文件都要求放到bin目录下或GAC中,而是可以放在任何位置,通过AppDomain.CurrentDomain.AssemblyResolve事件的处理函数来动态加载。

 


评论


亲,登录后才可以留言!