我的项目设置如下:
- “定义”项目
- “实现”项目
- “消费者”项目
“消费者”项目引用了“定义”和“实现”项目,但未在代码中静态地引用“实现”项目中的任何类型。
当应用程序启动时,“消费者”项目调用“定义”项目中的一个静态方法,该方法需要找到“实现”项目中的类型。
是否有一种办法可以强制将所有被引用的程序集加载到应用程序域中,而不必知道路径或名称,并且最好不需要使用全功能IOC框架?
我的项目设置如下:
“消费者”项目引用了“定义”和“实现”项目,但未在代码中静态地引用“实现”项目中的任何类型。
当应用程序启动时,“消费者”项目调用“定义”项目中的一个静态方法,该方法需要找到“实现”项目中的类型。
是否有一种办法可以强制将所有被引用的程序集加载到应用程序域中,而不必知道路径或名称,并且最好不需要使用全功能IOC框架?
这似乎解决了问题:
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();
toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
正如Jon所指出的,理想的解决方案需要递归地加载每个程序集的依赖项,但在我的特定情况下,我不必担心这个问题。
更新: .NET 4中包含的托管可扩展框架(System.ComponentModel)具有更好的功能来完成此类操作。
new DirectoryCatalog(".")
(需要引用System.ComponentModel.Composition
)。 - Allon Guralnekassembly.Location
,不过这个方法对于IsDynamic
程序集是不起作用的。 - Eli GassertAssembly.GetReferencedAssemblies
方法获取一个 AssemblyName[]
数组,然后对每个数组中的元素调用 Assembly.Load(AssemblyName)
方法进行加载。当然,你需要递归调用该方法,并且最好能够跟踪已经加载过的程序集 :)System.Reflection
)发现可用服务时,它自然无法找到尚未加载的程序集中包含的服务。我一直采用的默认方法是在我的应用程序的CompositionRoot中从每个引用程序集的任意类型创建一个虚拟子类,以确保所有依赖项都已就位。我希望我可以通过预先加载所有内容来跳过这些无聊的步骤,即使以进一步增加启动时间为代价。@JonSkeet还有其他方法吗?谢谢 - mfeineis我想分享一个递归的例子。我在启动程序中这样调用LoadReferencedAssembly方法:
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
this.LoadReferencedAssembly(assembly);
}
这是递归方法:
private void LoadReferencedAssembly(Assembly assembly)
{
foreach (AssemblyName name in assembly.GetReferencedAssemblies())
{
if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == name.FullName))
{
this.LoadReferencedAssembly(Assembly.Load(name));
}
}
}
name
不在AppDomain.CurrentDomain.GetAssemblies()
中时才运行递归,这意味着仅当foreach
挑选的AssemblyName
尚未加载时才会发生递归。 - FelypeO(n^2)
运行时间(foreach
中的GetAssemblies().Any(...)
))感到不满意。我会使用HashSet
将其降至O(n)
级别。 - Dai如果您使用 Fody.Costura 或其他程序集合并解决方案,则无法使用已接受的答案。
以下代码会加载任何当前已加载的程序集的引用程序集,但递归处理需要您自己实现。
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
loadedAssemblies
.SelectMany(x => x.GetReferencedAssemblies())
.Distinct()
.Where(y => loadedAssemblies.Any((a) => a.FullName == y.FullName) == false)
.ToList()
.ForEach(x => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(x)));
.Where
中检查 !y.IsDynamic
。 - Felypevar loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
没有返回未使用的程序集,因此根本不会传播它们。 - Ted今天我需要从特定路径加载一个程序集和它的依赖项,因此我编写了这个类来实现。
public static class AssemblyLoader
{
private static readonly ConcurrentDictionary<string, bool> AssemblyDirectories = new ConcurrentDictionary<string, bool>();
static AssemblyLoader()
{
AssemblyDirectories[GetExecutingAssemblyDirectory()] = true;
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
}
public static Assembly LoadWithDependencies(string assemblyPath)
{
AssemblyDirectories[Path.GetDirectoryName(assemblyPath)] = true;
return Assembly.LoadFile(assemblyPath);
}
private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
string dependentAssemblyName = args.Name.Split(',')[0] + ".dll";
List<string> directoriesToScan = AssemblyDirectories.Keys.ToList();
foreach (string directoryToScan in directoriesToScan)
{
string dependentAssemblyPath = Path.Combine(directoryToScan, dependentAssemblyName);
if (File.Exists(dependentAssemblyPath))
return LoadWithDependencies(dependentAssemblyPath);
}
return null;
}
private static string GetExecutingAssemblyDirectory()
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
如果您的程序集在编译时没有引用任何代码,那么即使您已将该项目或NuGet包添加为引用,这些程序集也不会作为其他程序集的引用被包含进来。这与Debug
或Release
构建设置、代码优化等无关。在这种情况下,您必须显式调用Assembly.LoadFrom(dllFileName)
来加载程序集。
另一种版本(基于Daniel Schaffer的答案)是当您可能不需要加载所有程序集,而只需要预定义数量的情况:
var assembliesToLoad = { "MY_SLN.PROJECT_1", "MY_SLN.PROJECT_2" };
// First trying to get all in above list, however this might not
// load all of them, because CLR will exclude the ones
// which are not used in the code
List<Assembly> dataAssembliesNames =
AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => AssembliesToLoad.Any(a => assembly.GetName().Name == a))
.ToList();
var loadedPaths = dataAssembliesNames.Select(a => a.Location).ToArray();
var compareConfig = StringComparison.InvariantCultureIgnoreCase;
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
.Where(f =>
{
// filtering the ones which are in above list
var lastIndexOf = f.LastIndexOf("\\", compareConfig);
var dllIndex = f.LastIndexOf(".dll", compareConfig);
if (-1 == lastIndexOf || -1 == dllIndex)
{
return false;
}
return AssembliesToLoad.Any(aName => aName ==
f.Substring(lastIndexOf + 1, dllIndex - lastIndexOf - 1));
});
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();
toLoad.ForEach(path => dataAssembliesNames.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
if (dataAssembliesNames.Count() != AssembliesToLoad.Length)
{
throw new Exception("Not all assemblies were loaded into the project!");
}
if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens
Microsoft.VisualBasic.Interaction.Beep();
// you can add more things here
}
编译器认为可能需要汇编,但实际上这当然永远不会发生。
这不是一个非常复杂的解决方案,但快速而有效。
要按名称获取引用的程序集,您可以使用以下方法:
public static Assembly GetAssemblyByName(string name)
{
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == name);
if (asm == null)
asm = AppDomain.CurrentDomain.Load(name);
return asm;
}
public static IEnumerable<Assembly> GetProjectAssemblies(string prefixName)
{
var assemblies = new HashSet<Assembly>
{
Assembly.GetEntryAssembly()
};
for (int i = 0; i < assemblies.Count; i++)
{
var assembly = assemblies.ElementAt(i);
var referencedProjectAssemblies = assembly.GetReferencedAssemblies()
.Where(assemblyName => assemblyName.FullName.StartsWith(prefixName))
.Select(assemblyName => Assembly.Load(assemblyName));
assemblies.UnionWith(referencedProjectAssemblies);
}
return assemblies;
}
ElementAt(i)
会迭代它,直到达到第 i
个元素,因此导致上面的代码总体上是 _O(N^2)_。我建议使用 foreach(var assembly in assemblies)
。 - vyrpHashSet
的元素不能保证以特定顺序排列。使用UnionWith
添加新元素后,元素是否会移动并且ElementAt(i)
不会返回您期望的内容?您几乎像使用一个无重复项的Queue
一样使用HashSet
。 - vyrp