在C#中如何获取程序集
作者:钱行慕
某一天我正在写一些反射代码,目的是遍历所有的程序集来查找一个特定的接口,然后在Startup中调用其上的一个方法。看起来这个功能似乎很简单,但是在现实中,却没有一个清晰的,简单的,适合各种情形的方式来获取一个程序集。这篇文章获取对某些人来说非常的枯燥,但是如果我能够帮助哪怕一个人来解决此类问题,那么这篇文章也是值得的。
说真的,由于有多种获取程序集的方法,我将不会说”使用这个方法”。很有可能,对于你的特定的工程来说,也许只有一种方式可以工作,所以依赖其他的方式是毫无意义的。让我们简单的对所有的方式做个测试,然后看看哪一个方法是最合理的。
使用AppDomain.GetAssemblies
你可能会遇到的第一个选项是AppDomain.GetAssemblies。它(看似)加载了在AppDomain中的所有程序集,基本上可以说加载了你项目使用到的每一个程序集。但是却存在着大量的警告。在.NET中程序集是延迟加载到AppDomain中的,它不可能一次性加载所有的程序集,而是等你调用了一个程序集中的方法/类的时候,它才会将它加载进来--也就是即时加载。这是合理的,因为如果你从不使用一个程序集的话,是没有理由加载它的。
但问题是在你调用AppDomain.GetAssemblies()的那个时间点上,如果你并没有调用某个特定的程序集的方法,它便不会被加载。现在如果你要为了Startup方法得到所有的程序集,很有可能你还没有调用到那个程序集,这就意味着它还没有加载到AppDomain,所以便获取不到这个接口方法。
用代码来说:
AppDomain.CurrentDomain.GetAssemblies(); // Does not return SomeAssembly as it hasn't been called yet. SomeAssembly.SomeClass.SomeMethod(); AppDomain.CurrentDomain.GetAssemblies(); // Will now return SomeAssembly.
虽然这看起来是一个很有吸引力的选项,但是要知道,对于这个方法来说,时机是一切。
使用AssemblyLoad事件
因为你不能确保当你调用CurrentDomain.GetAssemblies()时所有程序集都被加载了,而实际上当AppDomain加载另一个程序集的时候有一个事件会运行。基本上说,当一个程序集被延迟加载的时候,你可以被通知到。它看起来像是这样:
AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => { var assembly = args.LoadedAssembly; };
如果你只是想当程序集加载的时候检查下一些东西,那这或许是一个不错的解决方案,但是这个过程在某个特定的点并不是必然会发生(在你的.NET Core app的Startup.cs类中并不会发生)。
这个方法的另一个问题是到你添加你的事件处理器的那个时刻,并不能保证程序集还没有被加载(事实上它们很可能已经加载过了)。所以呢?你需要付出双份的努力,首先添加你的事件处理器,之后迅速的检查AppDomain.CurrentDomain.GetAssemblies,找到已经被加载了的东西。
这是一个完美的解决方案,但是如果你习惯于使用延迟加载的程序集来做事的话,这就不能正常工作了。
使用 GetReferencedAssemblies()
排名中的下一个是GetReferencedAssemblies()。本质上你可以通过一个程序集,比如你的入口点程序集,一般来说便是你的web项目,来得到所有引用的程序集。
其代码本身看起来像是这样:
Assembly.GetEntryAssembly().GetReferencedAssemblies();
再一次,看起来像是在玩把戏,但是这个方法有另一个很大的问题。在许多项目中会有一个“模式分离”的概念,比如 Web Project>>Service Project>>Data Project。Web Project本身并不直接引用Data Project。而当你调用“GetReferencedAssemblies”时其意味着直接引用。因此如果你期望在程序集列表中得到Data Project,你将会失望不已。
所以,再一次的,在一些情况下可以正常工作,但并不是一个普遍的解决方案。
循环GetReferencedAssemblies()
使用GetReferencedAssemblies()的另一个选择是创建一个方法来遍历所有的程序集。类似于这样:
public static List GetAssemblies() { var returnAssemblies = new List(); var loadedAssemblies = new HashSet(); var assembliesToCheck = new Queue(); assembliesToCheck.Enqueue(Assembly.GetEntryAssembly()); while(assembliesToCheck.Any()) { var assemblyToCheck = assembliesToCheck.Dequeue(); foreach(var reference in assemblyToCheck.GetReferencedAssemblies()) { if(!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); returnAssemblies.Add(assembly); } } } return returnAssemblies; }
这个方法的边界处理得有点粗糙,但是它的确可以工作并且意味着在Startup方法中,你可以立即看到所有的程序集。
关于这个方法你可能会被卡住的一次是如果你正在动态加载程序集,并且它们实际上并不会被任何项目所引用。对于这种情况,你需要下一个方法。
目录DLL加载
一个很粗糙的获取所有解决方案dll的方式是将它们加载出你的bin文件夹。看起来像是这样:
public static Assembly[] GetSolutionAssemblies() { var assemblies = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x))); return assemblies.ToArray(); }
它可以正常工作但的确是一个粗糙的解决方案。但是使用这个方式的一个最大的好处是一个dll只需要简单的放置在需要加载的目录中就可以。因此如果你出于任何原因动态的加载DLLs,对于你来说,这很可能是唯一的方法(除过在AppDomain中监听AssemblyLoad)。
这是做这件事情的看起来像恶作剧的方式之一。但是很可能你已经被阻挡到角落之中而这正是解决问题的唯一方式。
仅仅得到“我的”程序集
使用这些方法中的任何一种,您会很快发现你正在将Nuget下的每个程序集加载到你的项目中,包括Nuget包、.NET核心库甚至运行时特定的dll。在.NET世界中,程序集就是程序集。没有“是的,但这是我的程序集”并且它们很特别的概念。
过滤的唯一方法就是检查名字。您可以将其作为白名单来执行,因此如果解决方案中的所有项目都以“MySolution”开头。因而你可以像这样来进行过滤:
Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(x => x.Name.StartsWith("MySolution."))
或者你可以选择一个黑名单选项,这个选项并不真正限制你的程序集,但至少可以减少你正在加载/检查/处理的程序集的数量。就像这样:
Assembly.GetEntryAssembly().GetReferencedAssemblies() .Where(x => !x.Name.StartsWith("Microsoft.") && !x.Name.StartsWith("System."))
黑名单可能看起来很蠢,但在某些情况下,如果您正在构建一个实际上不知道最终解决方案名称的库,那么这是减少您试图加载的内容的唯一方法。
到此这篇关于在C#中如何获取程序集的文章就介绍到这了,更多相关C#获取程序集内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!