在編寫開發框架的時候,經常會遇到要找出應用所用到的所有程序集和類,然後進行下一步的處理。
例如,我們有一個通用控件類BaseControl,各種富文本編輯器控件、表格控件、分頁控件等都繼承於通用控件類BaseControl。甚至CMS這個項目的評論等控件也會繼承該通用控件類BaseControl。我們有一個需求,就是要做一個下拉列表,列出所有的控件。因為控件會分散在不同的程序集中,這樣我們必然會搜索當前應用中的所有程序集,從中找出所有繼承於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>,可是如果用下拉列表展示,總不能顯示類似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 }
最終就可以通過TypeNameHelper.GetTypeNameHelperList<BaseControl>()就可以獲取所有的控件子類,子類列表存放在List<TypeNameHelperInfo>。