好的,在这方面取得了一些进展,除非我想编写自己的元数据解析器,否则看起来这就是我所坚持的,所以我想我会分享。我会尽量给出利弊。
这个答案都围绕使用事件处理程序
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve
事件。应该指出的是,只有在解决程序集的依赖关系时才会引发此事件。起初我不明白,但这只是意味着不会为您尝试加载的程序集触发事件,而是为该程序集中的任何依赖项触发事件,这是有道理的。当您首先必须有一个起点时,为什么要提出该事件。对于我的解决方案,当我调用 .GetTypes() 时将引发此事件。
在我发布代码之前,让我解释一下这个想法以及它的优缺点。这个想法是,如果我有依赖项,则将引发该事件。在这种情况下,我将首先检查在与原始程序集相同的文件夹(包括子文件夹)中找到的所有程序集的字典,该字典是事先创建的。如果在该字典中找到了 args.Name,那么我在该程序集中读取并从事件中返回它。如果它不在列表中,我会尝试仅按名称加载程序集,这意味着依赖程序集必须是项目的一部分或安装在本地 GAC 中。
我已经在这里暗示了一些缺点,所以让我们从这些开始。
因为我不只是查看元数据来告诉我主程序集中有哪些依赖项(换句话说,必须加载这些依赖项),如果在原始程序集中(在同一文件夹或子文件夹中)找不到依赖项的程序集,并且该依赖没有安装在本地 GAC 中,或者在项目中引用,GetTypes() 仍然会抛出 ReflectionTypeLoadException 异常。遇到依赖程序集不位于原始程序集或未安装在 GAC 中的情况似乎很少见,但我会以 PRISM 作为可能发生这种情况的示例。 PRISM 程序集不一定会安装到 GAC 中,也不一定会复制到本地。
那么这种方法有什么好处呢?主要是它至少为您提供了一些方法来处理 99% 的情况,即您手头没有依赖项的程序集。
代码之前的一两个最终想法(实际上是陷阱)。我使用 AssemblyName 根据程序集的全名创建字典,而无需实际加载(直接加载或通过 ReflectionOnlyLoad)程序集。有两种方法可以创建 AssemblyName 的实例,或者通过传递程序集路径的构造函数,或者使用静态方法 GetAssemblyName(path)。构造函数似乎只处理 GetAssemblyName() 可以处理 UNC 路径的本地路径。这些是陷阱:不要仅仅为了获取名称而加载程序集,并确保您不会将自己限制在本地路径中。
最后,一些代码:
public class TestClass
{
//This dictionary will hold a list of the full path for an assembly, indexed by the assembly's full name
private Dictionary<string, string> _allAsms = new Dictionary<string, string>();
/// <summary>
/// Tries to list all of the Types inside an assembly and any interfaces those types inherit from.
/// </summary>
/// <param name="pathName">The path of the original assembly, without the assembly file</param>
/// <param name="fileName">The name of the assembly file as it is found on disk.</param>
public void Search(string pathName, string fileName)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
//Getting a list of all possible assembly files that exist in the same location as the original assembly
string[] filePaths = Directory.GetFiles(pathName, "*.dll", SearchOption.AllDirectories);
Assembly asm;
AssemblyName name;
if (!pathName.EndsWith("\\"))
pathName += "\\";
foreach (string path in filePaths)
{
name = AssemblyName.GetAssemblyName(path);
if (!_allAsms.ContainsKey(name.FullName))
_allAsms.Add(name.FullName, path);
}
//This is where we are loading the originaly assembly
asm = System.Reflection.Assembly.ReflectionOnlyLoad(File.ReadAllBytes(pathName + fileName));
Console.WriteLine("Opened assembly:{0}", fileName);
//And this is where the ReflectionOnlyAssemblyResolve will start to be raised
foreach (Type t in asm.GetTypes())
{
Console.WriteLine(" " + t.FullName);
//Get the interfaces for the type;
foreach (Type dep in t.GetInterfaces())
{
Console.WriteLine(" " + dep.FullName);
}
}
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
if (_allAsms.ContainsKey(args.Name))
return Assembly.ReflectionOnlyLoad(File.ReadAllBytes(_allAsms[args.Name]));
else
return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
}
}