有没有办法强制将所有引用的程序集加载到应用程序域中?

问题描述 投票:0回答:11

我的项目是这样设置的:

  • 项目“定义”
  • 项目“实施”
  • “消费者”项目

项目“Consumer”同时引用“Definition”和“Implementation”,但不静态引用“Implementation”中的任何类型。

应用程序启动时,项目“Consumer”调用“Definition”中的静态方法,需要在“Implementation”中查找类型

有没有一种方法可以在不知道路径或名称的情况下强制将任何引用的程序集加载到应用程序域中,并且最好不必使用成熟的 IOC 框架?

c# assemblies appdomain
11个回答
104
投票

这似乎成功了:

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) 具有更好的设施来完成此类任务。


68
投票

您可以使用

Assembly.GetReferencedAssemblies
来获取
AssemblyName[]
,然后对每个对象调用
Assembly.Load(AssemblyName)
。当然,您需要递归 - 但最好跟踪您已经加载的程序集:)


24
投票

只是想分享一个递归示例。我在启动例程中调用 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));
        }
    }
}

16
投票

如果您使用 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)));

2
投票

由于我今天必须从特定路径加载程序集+依赖项,所以我编写了这个类来做到这一点。

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);
    }
}

0
投票

另一个版本(基于 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!");
}

0
投票

如果您的程序集在编译时未引用任何代码,则即使您已添加项目或 nuget 包作为引用,这些程序集也不会作为对其他程序集的引用包含在内。这与

Debug
Release
构建设置、代码优化等无关。在这些情况下,您必须显式调用
Assembly.LoadFrom(dllFileName)
来加载程序集。


0
投票

要按名称获取引用的程序集,您可以使用以下方法:

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;
}

0
投票

在我的winforms应用程序中,我为JavaScript(在WebView2控件中)提供了调用各种.NET事物的可能性,例如程序集Microsoft.VisualBasic.dll中的

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
}

编译器认为可能需要程序集,但实际上这当然不会发生。

这不是一个非常复杂的解决方案,但又快又脏。


0
投票

如果您想要将前面示例的简化版本整齐地打包为

AppDomain.CurrentDomain
的扩展方法,这里有一个可以轻松指定您想要强制加载的任何/所有(通过参数数组)引用的程序集(如果尚未加载)....

此扩展方法兼容netstandard2.0...

实现为:

//Ensure that the assembly is loaded (since it is dynamically accessed and may not yet be initialized)...
AppDomain.CurrentDomain.ForceLoadAssemblies("MyDynamicAssemblies.AssemblyName1", "MyDynamicAssemblies.AssemblyName2");

/// <summary>
/// Force load all or specified Assemblies via the filter params.
/// Adapted from original Stack Overflow answer here: https://stackoverflow.com/a/2384679/7293142
/// </summary>
/// <param name="appDomain"></param>
/// <param name="assemblyNames"></param>
public static void ForceLoadAssemblies(this AppDomain appDomain, params string[] assemblyNames)
{
    var loadedAssemblyPaths = appDomain.GetAssemblies()
        .Where(a => !a.IsDynamic)
        .Select(a => a.Location)
        .ToArray();
    
    var assemblyNamesHashSet = new HashSet<string>(assemblyNames, StringComparer.InvariantCultureIgnoreCase);
    var referencedPaths = Directory.GetFiles(appDomain.BaseDirectory, "*.dll");
    
    var pathsToLoad = referencedPaths
        .Where(p => 
            assemblyNamesHashSet.Count == 0 //<== Load ALL Assemblies
            || assemblyNamesHashSet.Contains(Path.GetFileName(p)) //<== Support Full File Name & Extension matches... 
            || assemblyNamesHashSet.Contains(Path.GetFileNameWithoutExtension(p))) //<== Support File Name only (no Extension) matches... 
        .Except(loadedAssemblyPaths, StringComparer.InvariantCultureIgnoreCase)
        .ToList();

    pathsToLoad.ForEach(p => appDomain.Load(AssemblyName.GetAssemblyName(p)));
}

-1
投票

我根据 @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;
}
© www.soinside.com 2019 - 2024. All rights reserved.