我正在创建一个控制台应用程序,它将在指定的目录中查找并加载实现给定IRqServiceWorker接口的所有程序集(dll文件),该接口只定义了一个void DoWork(字符串参数)函数。问题是这个目录将包含大量的汇编文件,其中大部分没有实现接口,所以我不希望它们全部加载到内存中。所以基本上,我首先要查找哪些程序集实现了接口,将它们保存到列表中,然后加载该列表中的所有程序集。
对于我的第一次尝试,我只是加载每个程序集,检查它是否实现了我的接口,将它们保存到列表中,然后创建每个类的实例并在其上调用DoWork()函数。以下是执行此操作的代码:
static void Main(string[] args)
{
var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
var rqServiceWorkerAssemblyFilePaths = new List<string>();
var rqServiceWorkerInstances = new List<IRqServiceWorker>();
// Load all assemblies and record the ones that implement our IRqServiceWorker interface.
foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
{
try
{
// If this assembly contains a class that implements IRqServiceWorker, add it to our list.
var assembly = Assembly.LoadFrom(assemblyFilePath);
if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
}
catch (Exception ex)
{ }
}
// Create an instance of every class that implements IRqServiceWorker.
foreach (var assemblyFilePath in rqServiceWorkerAssemblyFilePaths)
{
try
{
// Get an instance of all types in this assembly that implement IRqServiceWorker and have a parameterless constructor.
var assembly = Assembly.LoadFile(assemblyFilePath);
var rqServiceWorkerTypes = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker)) && t.GetConstructor(Type.EmptyTypes) != null);
rqServiceWorkerInstances.AddRange(rqServiceWorkerTypes.Select(t => Activator.CreateInstance(t) as IRqServiceWorker).Where(i => i != null));
}
catch (Exception ex)
{ }
}
// Call the DoWork function on every class that implements IRqServiceWorker.
foreach (var instance in rqServiceWorkerInstances)
{
try
{
instance.DoWork(assembliesRootDirectory);
}
catch (Exception ex)
{ }
}
}
这样可行,但它最终将每个程序集加载到内存中,因此它会迅速增加我的应用程序的内存占用量。由于无法卸载程序集,因此正确的解决方案似乎是将程序集加载到新的临时应用程序域中,创建在那里实现IRqServiceWorker的程序集列表,然后卸载该临时应用程序域以从内存中卸载所有这些额外程序集。一旦我有了这个列表,我就可以加载我关心使用上述技术的程序集。除非有其他方法来检查dll是否实现了接口而没有将其加载到app域中?
我发现很多帖子谈论如何将程序集加载到新的应用程序域1 2 3 4 5 {{3} },但无法让它们中的任何一个工作。
以下是我尝试将程序集加载到新的应用程序域中的一种方法:
private static void Main(string[] args)
{
var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
var rqServiceWorkerAssemblyFilePaths = new List<string>();
var rqServiceWorkerInstances = new List<IRqServiceWorker>();
// Create a temporary app domain to load all assemblies into, and then unload it when we are done with it.
var tempAppDomain = AppDomain.CreateDomain("tempAppDomain");
foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
{
try
{
// Load the assembly into our temporary app domain.
var tempAssemblyName = AssemblyName.GetAssemblyName(assemblyFilePath);
var assembly = tempAppDomain.Load(tempAssemblyName);
// If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load into the real app domain.
if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
}
catch (Exception ex)
{}
}
// Unload the temp app domain to release all of the assemblies from memory.
AppDomain.Unload(tempAppDomain);
// Force garbage collection of the temp app domain we just unloaded.
GC.Collect();
GC.WaitForPendingFinalizers();
// Load only the assemblies we care about now.....
}
我还尝试使用AppDomainSetup创建新的app域:
var appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationName = "temp";
appDomainSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
appDomainSetup.PrivateBinPath = assembliesRootDirectory;
appDomainSetup.CachePath = Path.Combine(assembliesRootDirectory, "cache" + Path.DirectorySeparatorChar);
appDomainSetup.ShadowCopyFiles = "true";
appDomainSetup.ShadowCopyDirectories = assembliesRootDirectory;
var tempAppDomain = AppDomain.CreateDomain("tempAppDomain", null, appDomainSetup);
尝试使用继承自MarshalByRefObject的代理:
public class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw;
}
}
}
static void Main(string[] args)
{
var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
var rqServiceWorkerAssemblyFilePaths = new List<string>();
var rqServiceWorkerInstances = new List<IRqServiceWorker>();
var domainSetup = new AppDomainSetup { PrivateBinPath = assembliesRootDirectory };
var tempAppDomain = AppDomain.CreateDomain("temp", AppDomain.CurrentDomain.Evidence, domainSetup);
foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
{
try
{
var domain = (ProxyDomain)(tempAppDomain.CreateInstanceAndUnwrap(typeof(ProxyDomain).Assembly.FullName, typeof(ProxyDomain).FullName));
var assembly = domain.GetAssembly(assemblyFilePath);
// If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load into the real app domain.
if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
}
catch (Exception ex)
{ }
}
// Unload the temp app domain to release all of the assemblies from memory.
AppDomain.Unload(tempAppDomain);
// Force garbage collection of the temp app domain we just unloaded.
GC.Collect();
GC.WaitForPendingFinalizers();
// Load only the assemblies we care about now.....
}
但是所有这些方法都会在tempAppDomain.Load(tempAssemblyName);
和domain.GetAssembly(assemblyFilePath);
{&#34;无法加载文件或程序集&#39; Class1,Version = 1.0.0.0, Culture = neutral,PublicKeyToken = null&#39;或其中一个依赖项。该 系统找不到指定的文件。&#34;:&#34; Class1,Version = 1.0.0.0, Culture = neutral,PublicKeyToken = null&#34;}
Class1.dll是assemblyRootDirectory中的第一个程序集,它实现了IRqServiceWorker。我也会注意到assemblyRootDirectory在我的应用程序目录之外(例如,我的应用程序位于&#34; C:\ MyApp \ MyApp.exe&#34;以及&#34; C:\ Assemblies& #34;。)
对我在这里做错了什么的想法?或者,如果有更简单/更好的方法来查看dll是否实现了给定的接口?任何帮助/想法都表示赞赏。感谢。
使用D Stanley评论中链接中提供的信息,我能够找到解决方案。他的帖子是关于使用我不想做的MEF,但也提供了我需要的花絮,即使用6 Mono.Cecil。该软件包能够读取程序集的信息,而无需将其实际加载到内存中。
以下是我最终解决方案的完整代码:
static void Main(string[] args)
{
var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
var rqServiceWorkerAssemblyFilePaths = new List<string>();
var rqServiceWorkerInstances = new List<IRqServiceWorker>();
var assembliesToNotLoad = new List<string>()
{
"RQ.ServiceWorker.Required.dll"
};
// Loop through all of the assembly files and record which ones implement IRqServiceWorker.
foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
{
// Skip processing the RQ.ServiceWorker.Required assembly that contains the IRqServiceWorker interface.
if (assembliesToNotLoad.Contains(Path.GetFileName(assemblyFilePath)))
continue;
try
{
// Read the assembly info without loading the assembly into memory, by using the Mono.Cecil package.
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyFilePath);
// If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load.
if (assemblyDefinition.Modules.Any(m => m.Types.Any(t => t.IsClass && t.Interfaces.Any(i => i.FullName.Equals(typeof(IRqServiceWorker).FullName)))))
rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
}
// Eat all exceptions.
catch (Exception ex)
{ }
}
// Force garbage collection to clear the memory allocated for all of the assembly definitions read above.
GC.Collect();
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(10); // Sleeping the thread allows the garbage collection to cleanup more garbage sooner for whatever reason.
// Load all of the assemblies that have a class that implements IRqServiceWorker.
foreach (var assemblyFilePath in rqServiceWorkerAssemblyFilePaths)
{
try
{
// Get an instance of all types in this assembly that implement IRqServiceWorker and have a parameterless constructor.
var assembly = Assembly.LoadFile(assemblyFilePath);
var rqServiceWorkerTypes = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker)) && t.GetConstructor(Type.EmptyTypes) != null);
rqServiceWorkerInstances.AddRange(rqServiceWorkerTypes.Select(t => Activator.CreateInstance(t) as IRqServiceWorker).Where(i => i != null));
}
catch (Exception ex)
{ }
}
// Loop over every class that implements IRqServiceWorker and call DoWork() on it.
foreach (var instance in rqServiceWorkerInstances)
{
try
{
instance.DoWork(assembliesRootDirectory);
}
catch (Exception ex)
{ }
}
}
因此,虽然我没有解决我尝试将程序集加载到不同的应用程序域(即使用纯粹的.Net代码)时遇到的问题,但我觉得这是一个更优雅的解决方案。谢谢你的帮助!