动态加载程序集
在我尝试搭建一个高度抽象的企业级三层架构时,我运用了反射的原理来解除框架间层次的耦合,有兴趣的朋友可以点击这里,我的核心代码如下
using IDal; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Factory { public class OrderDALFactory { private static readonly string AssemblyName = ConfigurationManager.AppSettings["Assembly"]; private static readonly string className = ConfigurationManager.AppSettings["className"]; public static IOrder CreateOrder() { return (IOrder)Assembly.Load(AssemblyName).CreateInstance(className); } } }
通过读取配置文件中的程序集路径和类的名称,利用反射动态的加载程序集并创建实例,从而达到解耦的目的,但是当我运行程序的时候,抛出了一个异常,大致的意思是无法加载到该程序集。这就引起了我的一点思考,什么样的程序集才能被反射的Load()方法加载,通过查找资料我大致明白程序集加载的一个套路:
1.对于强类型称的程序集(也就是除了程序集名称,还有一大堆用来唯一标识它的东西,如:MyType, Version=1.0.3087.28686, Culture=neutral, PublicKeyToken=337642649f453c2c)的加载会首先会访问全局程序集缓存(可以暂时理解文微软类库程序集存放的地方),如果没有找到则访问我们应用程序的工作目录,可以理解为程序的bin文件夹,如果还是没有找到则去私有目录找,至于私有目录则是我们用户自定义给程序的目录文件,我们可以在配置文件中配置,如下配置我们则指定了在当前程序根目录下的Modules文件夹为私有目录。
<configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="Modules"></probing> </assemblyBinding> </runtime> </configuration>
2.对于弱类型名称程序集(即除去标识只有名称的程序集如:MyType)则直接忽视全局程序集缓存的查找,直接找工作目录和私有目录,如果找的到则加载进来。
因此,很显然要解决我们程序集无法加载的异常只要把待加载的程序集在生成时的路径设置为当前应用的Bin文件夹就能解决,换句话说就是Bin文件夹里面必须要有待加载的程序集,不管该程序集有没有被引用。
我为什么要把反射动态加载程序集以及创建实例,拿出来单独讲,是因为这个功能相当的重要,运用面相当的广泛。举个例子在MVC的框架中,我们就是通过动态的加载程序集,然后遍历程序集里面所有继承了Controller的类型,最后在根据输入的URL与这些控制器的名称进行比较得到我们最终请求的控制器,知道了这个原理,那我们完全可以另起一个专门的类库项目,保存MVC控制器类型,使得程序结构更加的合理,如图:
我把MVC项目中的Controlls文件夹干掉了,新起了个项目存放我们MVC控制器。
反射在范型方面的运用
范型分为两种类型,一种是开放型,一种是闭合型。举个例子List<T>就是个开放型的,因为我们的T类型还不确定;List<int>就是个闭合型的,范型的类型参数已经确定了,下面我来看一看反射在范型的具体运用
1.范型的类型名称(公共运行库中的名称)
T = typeof(List<>); Console.WriteLine(T); T = typeof(List<int>); Console.WriteLine(T);
从图上可以看出,范型在公共运行库中的名称和我们想象中的如:System.Collections.Generic.List<int>这样有所差别的。所以当我们尝试typeof(System.Collections.Generic.List<int>)会报错的。接着我们来分析下上面两个类名,其中‘1代表范型类型参数的个数;Dictionary<,>带两个参数就会是’2;‘1后面的类型名则代表范型的类型参数的名称了。但是对于开放型范型,它是没太大的意义,仅仅表示一个占位符,所以我们尝试typeof(System.Collections.Generic.List`1)是不会有任何问题的,相当于typeof(List<T>)
2.开放型范型和闭合型范型的相互转化。为什么要说这个,这是因为我们的开放型范型是没办法在程序中被实例化以及被调用的,真正到了运用的时候必须转化为闭合型范型的,如何转化如代码所示
Type t = typeof(List<>); //开放型 Console.WriteLine(t); t = t.MakeGenericType(typeof(int));//闭合型 Console.WriteLine(t); t = t.GetGenericTypeDefinition();//开放型 Console.WriteLine(t);
我们看到MakeGenericType()使我们的开放型范型转成了闭合型范型,T被int类型给替换了,而GetGenericTypeDefinition()则相反
3.创建范型实例。首先说明一点开放型范型是没办法实例化的,测试如下
try { Type T = typeof(List<>); //T = T.MakeGenericType(typeof(int)); List<int> obj = Activator.CreateInstance(T) as List<int>; Console.WriteLine("成功"); } catch { Console.WriteLine("失败"); }
而当我们把注释去掉,把开放型范型转化为闭合型,我们能成功创建范型实例。
4.范型的非范型方法调用。首先看实例方法,例如List<int>的Add方法,有两种途径:
Type T = typeof(List<>); T = T.MakeGenericType(typeof(int)); List<int> obj = Activator.CreateInstance(T) as List<int>; MethodInfo methodInfo= T.GetMethod("Add"); methodInfo.Invoke(obj,new object[]{1});
Type T = typeof(List<>); T = T.MakeGenericType(typeof(int)); List<int> obj = Activator.CreateInstance(T) as List<int>; obj.Add(1);
在看一看范型的静态方法的调用,我们新建一个RefTest<T>类,然后调用它的静态方法SayHello
public class RefTest<T> { public static T TestMethod<M>(M pars) { return default(T); } public static string SayHello() { return "hello"; } }
Type T = typeof(RefTest<>); T = T.MakeGenericType(typeof(string)); MethodInfo methodInfo = T.GetMethod("SayHello"); methodInfo.Invoke(null, new object[] {}); Console.WriteLine("成功");
5.范型的范型方法调用。要注意两点,第一我们的范型必须是闭合型的范型;第二点在调用范型方法的时候一定要为方法指定具体的类型,调用 MakeGenericMethod方法指定该范型方法的范型参数的具体类型。
Type T = typeof(RefTest<>); T = T.MakeGenericType(typeof(int)); MethodInfo methodInfo = T.GetMethod("TestMethod"); //特别注意MakeGenericMethod方法将返回闭合型的方法,只有闭合型方法才能被调用 methodInfo = methodInfo.MakeGenericMethod(typeof(string)); methodInfo.Invoke(null, new object[] { "hello" });
总结
本文强调了反射动态加载创建实体的重要性,并给出了反射动态加载程序集的一般规律,同时分享了反射在范型方面的一些运用,希望给大家带来帮助。