在编写开发框架的时候,经常会使用反射。反射的作用主要是动态创建类型的实例,或者获取对象类型并动态调用方法、属性、字段等。
我们在以前的.net framework框架中,反射工具包含了许多方法,但是在.net core中由于appdomain等变化的原因,许多方法已不再使用。我只将重反射工具类(ReflectionHelper)重要的几个方法说明一下。
在框架编写过程中,我们会遇到这样的需求:找出应用所用到的所有程序集和类,然后进行下一步的处理。
例如,我们有一个通用控件类BaseControl,各种富文本编辑器控件(RichText)、表格控件(Table)、分页控件(Paganitation)等都继承于通用控件类BaseControl。甚至CMS这个项目的评论(Comment)等控件也会继承该通用控件类BaseControl。我们需求是要做一个下拉列表,列出所有的控件。因为各个子控件会分散在不同的程序集中,评论控件就在CMS程序集中,这样我们必然会搜索当前应用中的所有程序集,从中找出所有继承于BaseControl的控件子类。这就是控件的列表。(如果我懒病不发作,能够写的够久的话,自定义表单、自定义查询等技术点可以看到这个需求。)
下面的方法是找到所有的应用程序集:
1 private static IEnumerableGetAssemblies() 2 { 3 List assemblies = new List (); 4 5 //以下2行,总是认为所有的个人程序集都依赖于core 6 Type type = typeof(ReflectionHelper); 7 8 var libs = DependencyContext.Default.CompileLibraries; 9 foreach (CompilationLibrary lib in libs)10 {11 //if (lib.Name.StartsWith("Microsoft") || lib.Name.StartsWith("System") || lib.Name.Contains(".System.") || lib.Name.StartsWith("NuGet") || lib.Name.StartsWith("AutoMapper")) continue;12 if (lib.Serviceable) continue;13 if (lib.Type == "package") continue;14 15 var assembly = Assembly.Load(new AssemblyName(lib.Name));16 assemblies.Add(assembly);17 18 //以下,总是认为所有的个人程序集都依赖于core19 20 ////过滤掉“动态生成的”21 //if (assembly.IsDynamic) continue;22 23 //if (assembly.FullName == type.AssemblyQualifiedName.Replace(type.FullName + ", ", ""))24 //{25 // assemblies.Add(assembly);26 // continue;27 //}28 29 //if (assembly.GetReferencedAssemblies().Any(ass => ass.FullName == type.AssemblyQualifiedName.Replace(type.FullName + ", ", "")))30 //{31 // assemblies.Add(assembly);32 //}33 }34 35 return assemblies;36 }
此处有个假设,第6行Type type = typeof(ReflectionHelper)。其中ReflectionHelper是核心应用程序集中的一个静态类,而核心应用程序集假设会被所有的应用程序集所引用。如果该假设不成立,需要将19-22行的注释去掉,针对每个找到的程序集获取所有引用的程序集。
if (lib.Serviceable) continue;和if (lib.Type == "package") continue; 这两行的意思是排除所有的系统程序集、Nuget下载包,减少搜索范围,提高效率。(这2行暂未最终确认。)
通过上面的程序,我们就可以从应用中找出所有的程序集。下一步从这些程序集中获取所有继承于BaseControl的控件子类。因为控件子类继承于BaseControl,因此子类所在的应用程序集必然引用BaseControl的应用程序集。从父类获取所有子类的方法如下:
1 #region 类型搜索 2 ///3 /// 获取子类型 4 /// 5 /// 父类型 6 ///7 public static IEnumerable GetSubTypes(Type type) 8 { 9 var assemblies = _Assemblies.Where(a =>10 {11 Assembly assembly = type.GetTypeInfo().Assembly;12 //基类所在程序集或依赖于基类的其他程序集13 return a.FullName == assembly.FullName || a.GetReferencedAssemblies().Any(ra => ra.FullName == assembly.FullName);14 });15 16 TypeInfo typeInfo = type.GetTypeInfo();17 18 return assemblies.SelectMany(a =>19 {20 return a.GetTypes().Where(t =>21 {22 if (type == t)23 {24 return false;25 }26 27 TypeInfo tInfo = t.GetTypeInfo();28 29 if (tInfo.IsAbstract || !tInfo.IsClass || !tInfo.IsPublic)30 {31 return false;32 }33 34 if (typeInfo.IsGenericTypeDefinition)35 {36 return type.IsAssignableFromGenericType(t);37 }38 39 return type.IsAssignableFrom(t);40 });41 });42 }43 44 /// 45 /// 获取子类型46 /// 47 ///父类型 48 ///49 public static IEnumerable GetSubTypes ()50 {51 return GetSubTypes(typeof(T));52 }53 #endregion
其中_Assemblies是从GetAssemblies()方法返回的结果。
这样就能够获取当前的子类列表IEnumerable<Type>。对于我们的需求,可以这样写ReflectionHelper.GetSubTypes<BaseControl>()。但是该方法的结果是IEnumerable<Type>,是Type的列表。我们如果用下拉列表展示,应该展示的是中文名称,总不能显示类似namespace.classname, assemblyname的样子吧,这样会被客户骂的。应该下拉出来的是中文名,例如富文本编辑器、文件上传、分页、自动完成等。
简单的做法是在BaseControl中增加一个抽象的Name属性,各个子类实现时override这个属性,标识该控件的中文名,倒是可以实现,不过在获取Name属性时,必须要实例化各个子类,天知道子类的构造函数有哪些参数。
我们的做法是建一个TypeNameAttribute,标识在各个子控件类上。具体实现如下:
1 ///2 /// 子类中,甚至TypeName,包括中英文及属性,以便反射使用 3 /// 4 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 5 public class TypeNameAttribute : Attribute 6 { 7 private string _Code, _Name, _Description; 8 ///9 /// 英文 10 /// 11 public string Code 12 { 13 get 14 { 15 return _Code; 16 } 17 set 18 { 19 _Code = value; 20 } 21 } 22 ///23 /// 中文 24 /// 25 public string Name 26 { 27 get 28 { 29 return _Name; 30 } 31 set 32 { 33 _Name = value; 34 } 35 } 36 ///37 /// 描述 38 /// 39 public string Description 40 { 41 get 42 { 43 return _Description; 44 } 45 set 46 { 47 _Description = value; 48 } 49 } 50 ///51 /// 构造函数 52 /// 53 /// 英文 54 /// 中文 55 /// 描述 56 public TypeNameAttribute(string code, string name, string description) 57 { 58 this._Code = code; 59 this._Name = name; 60 this._Description = description; 61 } 62 63 ///64 /// 构造函数 65 /// 66 /// 英文 67 /// 中文 68 public TypeNameAttribute(string code, string name) : this(code, name, string.Empty) 69 { 70 } 71 } 72 73 ///74 /// TypeName的工具类 75 /// 76 public static class TypeNameHelper 77 { 78 public static ConcurrentDictionary> list = new ConcurrentDictionary >(); 79 80 public static TypeNameHelperInfo GetTypeNameHelperInfo (string code) 81 { 82 List list = GetTypeNameHelperList (); 83 84 return list.SingleOrDefault(info => info.Code == code); 85 } 86 87 /// 88 /// 根据基类,获取所有子类的TypeName 89 /// 90 ///基类型 91 ///子类的TypeName信息 92 public static ListGetTypeNameHelperList () 93 { 94 if (list.ContainsKey(typeof(T))) 95 { 96 return list[typeof(T)]; 97 } 98 99 List result = new List ();100 101 IEnumerable typeList = ReflectionHelper.GetSubTypes ();102 103 foreach (Type type in typeList)104 {105 try106 {107 TypeNameAttribute attribute = ReflectionHelper.GetCustomAttribute (type);108 result.Add(new TypeNameHelperInfo()109 {110 Code = attribute.Code,111 Name = attribute.Name,112 Description = attribute.Description,113 Type = type114 });115 }116 catch117 {118 }119 }120 121 list[typeof(T)] = result;122 123 return result;124 }125 }126 127 /// 128 /// TypeName的信息类129 /// 130 public class TypeNameHelperInfo131 {132 ///133 /// 英文134 /// 135 public string Code { get; set; }136 ///137 /// 中文138 /// 139 public string Name { get; set; }140 ///141 /// 描述142 /// 143 public string Description { get; set; }144 ///145 /// 类型146 /// 147 public Type Type { get; set; }148 }
例如自动完成控件就可以如下写法:
1 ///2 /// 自动填充下拉框控件3 /// 4 [TypeName("AutoComplete", "自动填充下拉框")]5 public class AutoComplete : BaseControl6 {7 ...8 }
最终就可以通过TypeNameHelper.GetTypeNameHelperList<BaseControl>()就可以获取所有的控件子类,子类列表存放在List<TypeNameHelperInfo>,绑定到select标签即可。