自动获取所有引用的有效程序集(程序集反射辅助类) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 namespace YU.Commons { public static class ReflectionHelper { public static IEnumerable<Assembly> GetAssembliesByProductName (string productName ) { var asms = AppDomain.CurrentDomain.GetAssemblies(); foreach (var asm in asms) { var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>(); if (asmCompanyAttr != null && asmCompanyAttr.Company == productName) { yield return asm; } } } private static bool IsSystemAssembly (Assembly asm ) { var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>(); if (asmCompanyAttr == null ) { return false ; } else { string companyName = asmCompanyAttr.Company; return companyName.Contains("Microsoft" ); } } private static bool IsSystemAssembly (string asmPath ) { var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(asmPath); var assembly = moduleDef.Assembly; if (assembly == null ) { return false ; } var asmCompanyAttr = assembly.CustomAttributes.FirstOrDefault(c => c.Constructor?.DeclaringType?.FullName == typeof (AssemblyCompanyAttribute).FullName); if (asmCompanyAttr == null ) { return false ; } var companyName = ((AsmResolver.Utf8String)asmCompanyAttr.Signature?.FixedArguments[0 ]?.Element)?.Value; if (companyName == null ) { return false ; } return companyName.Contains("Microsoft" ); } private static bool IsManagedAssembly (string file ) { using var fs = File.OpenRead(file); using PEReader peReader = new PEReader(fs); return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly; } private static Assembly? TryLoadAssembly(string asmPath) { AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath); Assembly? asm = null ; try { asm = Assembly.Load(asmName); } catch (BadImageFormatException ex) { Debug.WriteLine(ex); } catch (FileLoadException ex) { Debug.WriteLine(ex); } if (asm == null ) { try { asm = Assembly.LoadFile(asmPath); } catch (BadImageFormatException ex) { Debug.WriteLine(ex); } catch (FileLoadException ex) { Debug.WriteLine(ex); } } return asm; } public static IEnumerable<Assembly> GetAllReferencedAssemblies (bool skipSystemAssemblies = true ) { Assembly? rootAssembly = Assembly.GetEntryAssembly(); if (rootAssembly == null ) { rootAssembly = Assembly.GetExecutingAssembly(); } var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality()); var loadedAssemblies = new HashSet<string >(); var assembliesToCheck = new Queue<Assembly>(); assembliesToCheck.Enqueue(rootAssembly); if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false ) { if (!IsValid(rootAssembly)) { returnAssemblies.Add(rootAssembly); } } while (assembliesToCheck.Any()) { var assemblyToCheck = assembliesToCheck.Dequeue(); foreach (var reference in assemblyToCheck.GetReferencedAssemblies()) { if (!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); if (skipSystemAssemblies && IsSystemAssembly(assembly)) { continue ; } assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); if (IsValid(assembly)) { returnAssemblies.Add(assembly); } } } } var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll" , new EnumerationOptions { RecurseSubdirectories = true }); foreach (var asmPath in asmsInBaseDir) { if (!IsManagedAssembly(asmPath)) { continue ; } AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath); if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName))) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asmPath)) { continue ; } Assembly? asm = TryLoadAssembly(asmPath); if (asm == null ) { continue ; } if (!IsValid(asm)) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asm)) { continue ; } returnAssemblies.Add(asm); } return returnAssemblies.ToArray(); } private static bool IsValid (Assembly asm ) { try { asm.GetTypes(); asm.DefinedTypes.ToList(); return true ; } catch (ReflectionTypeLoadException) { return false ; } } } class AssemblyEquality : EqualityComparer <Assembly > { public override bool Equals (Assembly? x, Assembly? y ) { if (x == null && y == null ) return true ; if (x == null || y == null ) return false ; return AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName()); } public override int GetHashCode ([DisallowNull] Assembly obj ) { return obj.GetName().FullName.GetHashCode(); } } }
总体结构说明 这个类提供了:
GetAssembliesByProductName :获取当前已经加载的程序集中,公司名等于指定的程序集。IsSystemAssembly(Assembly)/IsSystemAssembly(string) :判断是否为系统程序集(通过公司名判断)IsManagedAssembly :判断某.dll文件是否为托管程序集。TryLoadAssembly :安全加载一个程序集。GetAllReferencedAssemblies :主方法:递归获取所有引用程序集,并加载当前目录中有效.dll文件。IsValid :判断程序集是否有效(能否获取类型)。AssemblyEquality :对程序集进行比较,避免重复添加。
分析每个部分功能与逻辑 GetAssembliesByProductName 1 2 3 4 5 6 7 8 9 10 11 12 13 public static IEnumerable<Assembly> GetAssembliesByProductName (string productName ) { var asms = AppDomain.CurrentDomain.GetAssemblies(); foreach (var asm in asms) { var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>(); if (asmCompanyAttr != null && asmCompanyAttr.Company == productName) { yield return asm; } } }
Enumerable<Assembly> 是一个泛型可枚举类型,表示一个 可以遍历的程序集(System.Reflection.Assembly)集合,通常用于反射操作中,比如加载、筛选、注册程序集等场景。 从当前应用域中获取所有已加载的程序集(AppDomain.CurrentDomain.GetAssembles()) 检查每个程序集的[AssemblyCompany]特性(公司名) 返回公司名等于指定值的程序集 用于过滤出“公司内开发”的程序集。
IsSystemAssembly(重载方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 private static bool IsSystemAssembly (Assembly asm ) { var asmCompanyAttr = asm.GetCustomAttribute<AssemblyCompanyAttribute>(); if (asmCompanyAttr == null ) { return false ; } else { string companyName = asmCompanyAttr.Company; return companyName.Contains("Microsoft" ); } }
IsSystemAssembly(Assembly asm)通过AssemblyCompanyAttribute判断是否包含“Microsoft”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static bool IsSystemAssembly (string asmPath ) { var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(asmPath); var assembly = moduleDef.Assembly; if (assembly == null ) { return false ; } var asmCompanyAttr = assembly.CustomAttributes.FirstOrDefault(c => c.Constructor?.DeclaringType?.FullName == typeof (AssemblyCompanyAttribute).FullName); if (asmCompanyAttr == null ) { return false ; } var companyName = ((AsmResolver.Utf8String)asmCompanyAttr.Signature?.FixedArguments[0 ]?.Element)?.Value; if (companyName == null ) { return false ; } return companyName.Contains("Microsoft" ); }
IsSystemAssembly(string asmPath)使用AsmResolver读取.dll元数据,再检查是否是系统程序集。assembly.CustomAttributes :获取程序集定义上的所有自定义特性(如 [AssemblyCompany], [AssemblyTitle] 等).FirstOrDefault(…) :从中取出第一个符合条件的特性,找不到就返回nullc.Constructor?.DeclaringType?.FullName :获取特性的构造函数所属类型的完整名称(及特性类型本身的FullName)typeof(AssemblyCompanyAattribute).FullName :获取AssemblyCompanyAttribute的完整名称,即 “System.Reflection.AssemblyCompanyAttribute”
asmCompanyAttr.Signature :这是一个 CustomAttributeSignature 对象,表示这个特性的构造函数签名,包括参数类型、值等。.FixedArguments[0] :这是获取第一个固定参数。对于 [AssemblyCompany(“OpenAI”)],这个第一个参数就是 “OpenAI”。.Element :FixedArguments[0].Element 是这个参数的值,类型是 ICustomAttributeType,但在字符串特性的场景下,它其实就是 Utf8String 类型的对象。(AsmResolver.Utf8String)… :将 Element 显式转换为 Utf8String,这是 AsmResolver 用来表示字符串的一种类型(和 .NET 的 string 类似,但内部是 UTF-8 编码结构)。?.Value :取出字符串的值。
这两个方法允许你在这两种场景下都判断程序集是否为“系统提供”(避免处理不必要的系统程序集)
IsManagedAssembly 1 2 3 4 5 6 private static bool IsManagedAssembly (string file ) { using var fs = File.OpenRead(file); using PEReader peReader = new PEReader(fs); return peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly; }
使用 System.Reflection.PortableExecutable.PEReader 判断 .dll 是否是托管程序集 核心判断是:是否有元数据 (peReader.HasMetadata) 且为程序集 (IsAssembly)
peReader.HasMetadata :文件中包含 .NET 元数据(说明是托管代码的一部分)IsAssembly :文件表示的是一个 程序集(即 .dll 或 .exe 中包含 Assembly 清单)
这样可以避免尝试加载本地DLL、C++DLL等非.NET程序集。
TryLoadAssembly 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private static Assembly? TryLoadAssembly(string asmPath) { AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath); Assembly? asm = null ; try { asm = Assembly.Load(asmName); } catch (BadImageFormatException ex) { Debug.WriteLine(ex); } catch (FileLoadException ex) { Debug.WriteLine(ex); } if (asm == null ) { try { asm = Assembly.LoadFile(asmPath); } catch (BadImageFormatException ex) { Debug.WriteLine(ex); } catch (FileLoadException ex) { Debug.WriteLine(ex); } } return asm; }
使用 Assembly.Load(AssemblyName) 加载 若失败,则尝试 Assembly.LoadFile 加载 捕获并记录常见错误,如 BadImageFormatException 和 FileLoadException
AssemblyName.GetAssemblyName(asmPath) :这一步尝试从文件中读取程序集的元信息,比如它的名称、版本、文化、公钥等。不会真正加载程序集,只是获取它的标识。Assembly.Load(asmName) :这是根据程序集名称(而不是文件路径)尝试从 当前 AppDomain 的探测路径中加载程序集。如果已经加载过,它会直接返回。 BadImageFormatException: 文件不是有效的 .NET 程序集(可能是原生 DLL) FileLoadException: 可能是文件锁定、权限问题、版本冲突等Assembly.LoadFile(asmPath) :如果第一次加载失败(返回 null),就使用 Assembly.LoadFile 直接从绝对路径加载程序集。
避免程序因为一个非法的 .dll 崩溃,同时仍尽可能加载更多程序集。
IsValid 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static bool IsValid (Assembly asm ){ try { asm.GetTypes(); asm.DefinedTypes.ToList(); return true ; } catch (ReflectionTypeLoadException ex) { Debug.WriteLine($"程序集加载失败: {asm.FullName} - {ex.Message} " ); return false ; } }
通过调用 GetTypes 和 DefinedTypes 来确保程序集中的类型是可访问的 捕获 ReflectionTypeLoadException 以处理不完整的程序集
AssemblyEquality 1 2 3 4 5 6 7 8 9 10 11 12 13 class AssemblyEquality : EqualityComparer <Assembly >{ public override bool Equals (Assembly? x, Assembly? y ) { if (x == null && y == null ) return true ; if (x == null || y == null ) return false ; return AssemblyName.ReferenceMatchesDefinition(x.GetName(), y.GetName()); } public override int GetHashCode ([DisallowNull] Assembly obj ) { return obj.GetName().FullName.GetHashCode(); } }
比较两个程序集是否相等,使用 AssemblyName.ReferenceMatchesDefinition 来确保版本等信息一致 用 AssemblyName.ReferenceMatchesDefinition 判断两个程序集是否“等价”,并且基于 AssemblyName.FullName 提供稳定的 HashCode。 用于 HashSet<Assembly> 去重时,自定义判断逻辑。
GetAllReferencedAssemblies 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public static IEnumerable<Assembly> GetAllReferencedAssemblies (bool skipSystemAssemblies = true ){ Assembly? rootAssembly = Assembly.GetEntryAssembly(); if (rootAssembly == null ) { rootAssembly = Assembly.GetExecutingAssembly(); } var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality()); var loadedAssemblies = new HashSet<string >(); var assembliesToCheck = new Queue<Assembly>(); assembliesToCheck.Enqueue(rootAssembly); if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false ) { if (!IsValid(rootAssembly)) { returnAssemblies.Add(rootAssembly); } } while (assembliesToCheck.Any()) { var assemblyToCheck = assembliesToCheck.Dequeue(); foreach (var reference in assemblyToCheck.GetReferencedAssemblies()) { if (!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); if (skipSystemAssemblies && IsSystemAssembly(assembly)) { continue ; } assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); if (IsValid(assembly)) { returnAssemblies.Add(assembly); } } } } var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll" , new EnumerationOptions { RecurseSubdirectories = true }); foreach (var asmPath in asmsInBaseDir) { if (!IsManagedAssembly(asmPath)) { continue ; } AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath); if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName))) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asmPath)) { continue ; } Assembly? asm = TryLoadAssembly(asmPath); if (asm == null ) { continue ; } if (!IsValid(asm)) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asm)) { continue ; } returnAssemblies.Add(asm); } return returnAssemblies.ToArray(); }
初始化 取入口程序集Assembly.GetEntryAssembly(),找不到就用当前正在执行的程序集。 创建 returnAssemblies 集合用于去重和存放结果 创建队列 assembliesToCheck 广度优先遍历依赖关系
1 2 3 4 var returnAssemblies = new HashSet<Assembly>(new AssemblyEquality()); var loadedAssemblies = new HashSet<string >(); var assembliesToCheck = new Queue<Assembly>(); assembliesToCheck.Enqueue(rootAssembly);
assembliesToCheck.Enqueue(rootAssembly) :把根程序集放入队列 assembliesToCheck 中,作为后续要处理的第一个程序集。
检查程序集是否加入
1 2 3 4 5 6 7 if (skipSystemAssemblies && IsSystemAssembly(rootAssembly) != false ){ if (!IsValid(rootAssembly)) { returnAssemblies.Add(rootAssembly); } }
如果启用了跳过系统程序集(skipSystemAssemblies = true),并且 rootAssembly 被识别为系统程序集,但它又是“无效”的,那就强制加入这个程序集。
递归加载引用程序集
遍历当前队列中的程序集引用,加载未加载的引用程序集。 使用 Assembly.Load(AssemblyName) 加载引用。 加入队列继续递归。 判断是否为系统程序集、是否有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 while (assembliesToCheck.Any()){ var assemblyToCheck = assembliesToCheck.Dequeue(); foreach (var reference in assemblyToCheck.GetReferencedAssemblies()) { if (!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); if (skipSystemAssemblies && IsSystemAssembly(assembly)) { continue ; } assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); if (IsValid(assembly)) { returnAssemblies.Add(assembly); } } } }
while (assembliesToCheck.Any()) : 这是 广度优先遍历 的主循环条件: assembliesToCheck 是一个 Queue<Assembly>,存储待处理的程序集;只要队列不为空,就继续处理。var assemblyToCheck = assembliesToCheck.Dequeue(); :从队列中取出一个待检查的程序集。assemblyToCheck.GetReferencedAssemblies() :获取当前程序集引用的所有其他程序集.if (!loadedAssemblies.Contains(reference.FullName)) :判断当前引用程序集是否已处理过: loadedAssemblies 是一个 HashSet<string>; 避免重复加载和无限循环。var assembly = Assembly.Load(reference) :尝试根据名称加载程序集: 使用 Assembly.Load(AssemblyName) 会从当前的应用域中加载已有的,或从 probing path 中查找。if (skipSystemAssemblies && IsSystemAssembly(assembly)) :跳过系统程序集的条件判断: 如果用户设置了 skipSystemAssemblies = true; 并且当前程序集是系统程序集(比如 Microsoft、System 开头); 那就 continue,直接跳过不处理。assembliesToCheck.Enqueue(assembly) :将新加载的程序集加入待处理队列,递归处理它的引用程序集。loadedAssemblies.Add(reference.FullName) :标记这个程序集已经处理过。if (IsValid(assembly)) { returnAssemblies.Add(assembly); } :最后判断这个程序集是否是“有效”的(没有类型加载错误等),是的话加入最终结果集中。
额外加载当前目录下的 DLL 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var asmsInBaseDir = Directory.EnumerateFiles(AppContext.BaseDirectory, "*.dll" , new EnumerationOptions { RecurseSubdirectories = true }); foreach (var asmPath in asmsInBaseDir){ if (!IsManagedAssembly(asmPath)) { continue ; } AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath); if (returnAssemblies.Any(x => AssemblyName.ReferenceMatchesDefinition(x.GetName(), asmName))) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asmPath)) { continue ; } Assembly? asm = TryLoadAssembly(asmPath); if (asm == null ) { continue ; } if (!IsValid(asm)) { continue ; } if (skipSystemAssemblies && IsSystemAssembly(asm)) { continue ; } returnAssemblies.Add(asm); } return returnAssemblies.ToArray();
遍历当前应用目录的所有 .dll 判断是否为托管程序集 判断是否为系统程序集 判断是否已经加载 通过 TryLoadAssembly 尝试加载 判断是否有效(能否访问类型定义)
这是为了把那些没有被主动引用,但仍在部署目录中的程序集加载进来。 使用 AppContext.BaseDirectory 获取运行目录,递归查找子目录中所有 .dll 文件 用 PEReader 判断是不是托管程序集 if (returnAssemblies.Any(x =>:防止重复加载同一个程序集(即使路径不同,只要 AssemblyName 一致,就视为相同)
使用场景
场景
示例
模块化注册
自动扫描实现了 IModuleInitializer 的类型并初始化
插件系统
加载插件目录的所有 .NET 程序集
自动依赖注入
根据命名空间、接口特征批量注册服务
动态发现服务
分布式架构中加载远程/本地服务模块