[英]Is there a way to force all referenced assemblies to be loaded into the app domain?
我的項目是這樣設置的:
項目“Consumer”同時引用“Definition”和“Implementation”,但不靜態引用“Implementation”中的任何類型。
應用程序啟動時,項目“Consumer”調用“Definition”中的靜態方法,需要在“Implementation”中查找類型
有沒有一種方法可以在不知道路徑或名稱的情況下強制將任何引用的程序集加載到應用程序域中,最好不必使用成熟的 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) 有更好的工具來完成這樣的事情。
您可以使用Assembly.GetReferencedAssemblies
來獲取AssemblyName[]
,然后對它們中的每一個調用Assembly.Load(AssemblyName)
。 當然,您需要遞歸 - 但最好跟蹤您已經加載的程序集:)
只是想分享一個遞歸的例子。 我在啟動例程中調用 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));
}
}
}
如果您使用 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)));
鑒於我今天必須從特定路徑加載程序集 + 依賴項,我編寫了這個類來完成它。
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);
}
}
要按名稱獲取引用的程序集,您可以使用以下方法:
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;
}
我根據@Jon Skeet 的答案創建了自己的名稱前綴過濾以避免加載不必要的程序集:
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;
}
另一個版本(基於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!");
}
如果您的程序集在編譯時未引用任何代碼,則這些程序集不會作為對其他程序集的引用包含在內,即使您已添加項目或 nuget 包作為引用。 這與Debug
或Release
構建設置、代碼優化等無關。在這些情況下,您必須顯式調用Assembly.LoadFrom(dllFileName)
以加載程序集。
在我的 winforms 應用程序中,我為 JavaScript(在 WebView2 控件中)提供了調用各種 .NET 事物的可能性,例如Microsoft.VisualBasic.Interaction
集中的 Microsoft.VisualBasic.Interaction 方法(例如InputBox()
等)。
但是我的應用程序本身不使用該程序集,因此永遠不會加載該程序集。
所以為了強制加載程序集,我最終只是在我的 Form1_Load 中添加了這個:
if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens
Microsoft.VisualBasic.Interaction.Beep();
// you can add more things here
}
編譯器認為可能需要程序集,但實際上這當然不會發生。
不是一個非常復雜的解決方案,但又快又臟。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.