在编写开发框架的时候,经常会使用反射。反射的作用主要是动态创建类型的实例,或者获取对象类型并动态调用方法、属性、字段等。
我们在以前的.net framework框架中,反射工具包含了许多方法,但是在.net core中由于appdomain等变化的原因,许多方法已不再使用。我只将重反射工具类(ReflectionHelper)重要的几个方法说明一下。
在框架编写过程中,我们会遇到这样的需求:找出应用所用到的所有程序集和类,然后进行下一步的处理。
例如,我们有一个通用控件类BaseControl,各种富文本编辑器控件(RichText)、表格控件(Table)、分页控件(Paganitation)等都继承于通用控件类BaseControl。甚至CMS这个项目的评论(Comment)等控件也会继承该通用控件类BaseControl。我们需求是要做一个下拉列表,列出所有的控件。因为各个子控件会分散在不同的程序集中,评论控件就在CMS程序集中,这样我们必然会搜索当前应用中的所有程序集,从中找出所有继承于BaseControl的控件子类。这就是控件的列表。(如果我懒病不发作,能够写的够久的话,自定义表单、自定义查询等技术点可以看到这个需求。)
下面的方法是找到所有的应用程序集:
1 private static IEnumerable<Assembly> GetAssemblies() 2 { 3 List<Assembly> assemblies = new List<Assembly>(); 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 //以下,总是认为所有的个人程序集都依赖于core 19 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 /// <summary> 3 /// 获取子类型 4 /// </summary> 5 /// <param name="type">父类型</param> 6 /// <returns></returns> 7 public static IEnumerable<Type> 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 /// <summary> 45 /// 获取子类型 46 /// </summary> 47 /// <typeparam name="T">父类型</typeparam> 48 /// <returns></returns> 49 public static IEnumerable<Type> GetSubTypes<T>() 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 /// <summary> 2 /// 子类中,甚至TypeName,包括中英文及属性,以便反射使用 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 5 public class TypeNameAttribute : Attribute 6 { 7 private string _Code, _Name, _Description; 8 /// <summary> 9 /// 英文 10 /// </summary> 11 public string Code 12 { 13 get 14 { 15 return _Code; 16 } 17 set 18 { 19 _Code = value; 20 } 21 } 22 /// <summary> 23 /// 中文 24 /// </summary> 25 public string Name 26 { 27 get 28 { 29 return _Name; 30 } 31 set 32 { 33 _Name = value; 34 } 35 } 36 /// <summary> 37 /// 描述 38 /// </summary> 39 public string Description 40 { 41 get 42 { 43 return _Description; 44 } 45 set 46 { 47 _Description = value; 48 } 49 } 50 /// <summary> 51 /// 构造函数 52 /// </summary> 53 /// <param name="code">英文</param> 54 /// <param name="name">中文</param> 55 /// <param name="description">描述</param> 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 /// <summary> 64 /// 构造函数 65 /// </summary> 66 /// <param name="code">英文</param> 67 /// <param name="name">中文</param> 68 public TypeNameAttribute(string code, string name) : this(code, name, string.Empty) 69 { 70 } 71 } 72 73 /// <summary> 74 /// TypeName的工具类 75 /// </summary> 76 public static class TypeNameHelper 77 { 78 public static ConcurrentDictionary<Type, List<TypeNameHelperInfo>> list = new ConcurrentDictionary<Type, List<TypeNameHelperInfo>>(); 79 80 public static TypeNameHelperInfo GetTypeNameHelperInfo<T>(string code) 81 { 82 List<TypeNameHelperInfo> list = GetTypeNameHelperList<T>(); 83 84 return list.SingleOrDefault(info => info.Code == code); 85 } 86 87 /// <summary> 88 /// 根据基类,获取所有子类的TypeName 89 /// </summary> 90 /// <typeparam name="T">基类型</typeparam> 91 /// <returns>子类的TypeName信息</returns> 92 public static List<TypeNameHelperInfo> GetTypeNameHelperList<T>() 93 { 94 if (list.ContainsKey(typeof(T))) 95 { 96 return list[typeof(T)]; 97 } 98 99 List<TypeNameHelperInfo> result = new List<TypeNameHelperInfo>(); 100 101 IEnumerable<Type> typeList = ReflectionHelper.GetSubTypes<T>(); 102 103 foreach (Type type in typeList) 104 { 105 try 106 { 107 TypeNameAttribute attribute = ReflectionHelper.GetCustomAttribute<TypeNameAttribute>(type); 108 result.Add(new TypeNameHelperInfo() 109 { 110 Code = attribute.Code, 111 Name = attribute.Name, 112 Description = attribute.Description, 113 Type = type 114 }); 115 } 116 catch 117 { 118 } 119 } 120 121 list[typeof(T)] = result; 122 123 return result; 124 } 125 } 126 127 /// <summary> 128 /// TypeName的信息类 129 /// </summary> 130 public class TypeNameHelperInfo 131 { 132 /// <summary> 133 /// 英文 134 /// </summary> 135 public string Code { get; set; } 136 /// <summary> 137 /// 中文 138 /// </summary> 139 public string Name { get; set; } 140 /// <summary> 141 /// 描述 142 /// </summary> 143 public string Description { get; set; } 144 /// <summary> 145 /// 类型 146 /// </summary> 147 public Type Type { get; set; } 148 }
例如自动完成控件就可以如下写法:
1 /// <summary> 2 /// 自动填充下拉框控件 3 /// </summary> 4 [TypeName("AutoComplete", "自动填充下拉框")] 5 public class AutoComplete : BaseControl 6 { 7 ... 8 }
最终就可以通过TypeNameHelper.GetTypeNameHelperList<BaseControl>()就可以获取所有的控件子类,子类列表存放在List<TypeNameHelperInfo>,绑定到select标签即可。