在編寫開發框架的時候,經常會遇到要找出應用所用到的所有程序集和類,然後進行下一步的處理。
例如,我們有一個通用控件類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>。