【问题标题】:Is there a way to force all referenced assemblies to be loaded into the app domain?有没有办法强制将所有引用的程序集加载到应用程序域中?
【发布时间】:2011-01-23 23:37:07
【问题描述】:

我的项目是这样设置的:

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

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

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

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

【问题讨论】:

  • 它会导致什么样的问题?为什么需要强制加载?
  • 根本没有加载,大概是因为没有静态依赖
  • 您是如何在实现中尝试“查找类型”的?您是否正在寻找实现特定接口的东西?
  • @Mike:是的。我正在做 AppDomain.CurrentDomain.GetAssemblies,并使用 linq 查询递归地调用 GetTypes()。

标签: c# assemblies appdomain


【解决方案1】:

这似乎可以解决问题:

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) 具有更好的工具来完成此类事情。

【讨论】:

  • 这对我不起作用,我引用的未加载的程序集不会显示在 AppDomain.CurrentDomain.GetAssemblies() 中。嗯...
  • 什么设施?我没有通过搜索找到任何东西。
  • 使用MEF,上面的代码可以缩短为:new DirectoryCatalog(".");(需要引用System.ComponentModel.Composition)。
  • 这个答案解决了我的问题并为我工作。我有一个 MS 单元测试项目引用了我的另一个程序集,并且 AppDomain.CurrentDomain.GetAssemblies() 在运行测试时没有返回该程序集。我怀疑即使我的单元测试使用该库中的代码,由于 vs.net 加载 MS 单元测试项目(类库)的方式与运行常规相比,程序集可能没有显示在“GetAssemblies”中.exe 应用程序。如果您的代码使用反射并且未通过单元测试,请记住一些事情。
  • 只是想添加,注意动态加载的程序集动态程序集中不支持调用的成员。过滤掉 IsDynamic = false 的程序集,或者如果您可以容错负载,请尝试/捕获您对 CurrentDomain.Load 的调用。和assembly.Location。那一个也需要检查。不适用于IsDynamic 程序集。
【解决方案2】:

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

【讨论】:

  • 我发现了,但问题是我必须从引用的程序集中做任何我正在做的事情......并且至少在单元测试的上下文中,GetCallingAssembly,GetExecutingAssembly 当然返回引用的程序集,GetEntryAssembly 返回 null :\
  • 如果您是在加载参考程序集之后,那么以上将解决您的问题。您还可以询问特定类型的 typeof(T).Assembly 是否有帮助。我有一种感觉,您需要的是动态加载包含实现(未引用)的程序集。如果是这种情况,您将不得不保留名称的静态列表并手动加载它们,或者遍历整个目录,加载然后找到具有正确接口的类型。
  • @vanhelgen:根据我的经验,您很少需要明确说明。通常 CLR 的“按需加载”工作正常。
  • 在正常情况下可能是这样,但是当使用 DI 容器(通过System.Reflection)发现可用服务时,它自然不会找到尚未加载的程序集中包含的服务。从那以后,我的默认方法是从我的应用程序的 CompositionRoot 中每个引用程序集的随机类型创建一个虚拟子类,以确保所有依赖项都到位。我希望我可以通过预先加载所有内容来跳过这种废话,即使以进一步增加启动时间为代价。 @JonSkeet 还有另一种方法吗?谢谢
  • 失败的地方是 GetReferencedAssemblies 显然返回了一个“优化”列表 - 因此,如果您没有在引用的程序集中显式调用代码,它将不会被包含在内。 (每this discussion
【解决方案3】:

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

【讨论】:

  • 我想知道循环程序集引用是否会导致抛出堆栈溢出异常。
  • Ronnie,我相信不是,代码仅在name 不在AppDomain.CurrentDomain.GetAssemblies() 中的情况下运行递归,这意味着它只会在foreach 选择的AssemblyName 不存在时才会递归尚未加载。
  • 我对这个算法的O(n^2) 运行时不满意(GetAssemblies().Any(...)foreach 中))。我会使用HashSet 将其降低到O(n) 的顺序。
【解决方案4】:

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

【讨论】:

  • 想知道这个 sn-p 应该去哪里?
  • 我想在你的引导加载程序/启动中。
  • 我可能错了,但我认为您可以在.Where 中检查!y.IsDynamic
  • 此代码 sn-p 不起作用。在我的测试中,最初的 var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); 不会返回未使用的程序集,因此根本不会传播它们。
  • 是的,这就是我所做的递归注释——需要加载依赖项的依赖项等——我已经有很多年没有研究这些东西了,也许现在有更好的方法。
【解决方案5】:

今天我必须从特定路径加载程序集 + 依赖项,所以我编写了这个类来完成它。

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

【讨论】:

  • 好的代码,除了不需要字典,在这种情况下,一个简单的列表就足够了。我假设您的原始代码需要知道哪些程序集已加载,哪些未加载,这就是您拥有 Dictionary 的原因。
【解决方案6】:

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

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

【讨论】:

    【解决方案7】:

    我根据@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;
    }
    

    【讨论】:

    • 在这种情况下它可能无关紧要,但我只是想指出,在集合上调用 ElementAt(i) 将对其进行迭代,直到它到达 ith 元素,因此导致代码以上为 O(N^2) 整体。我建议使用foreach(var assembly in assemblies)
    • @vyrp nop: System.InvalidOperationException: '集合已修改;枚举操作可能无法执行。'
    • 啊,我没有意识到 assemblies 正在被修改。那么是的,我的提议会引发异常。但是,代码不依赖于未记录的行为吗? HashSet 的元素不保证按特定顺序排列。使用UnionWith 添加新元素后,元素不能四处移动,ElementAt(i) 不能返回您所期望的吗?您正在使用 HashSet 几乎就像 Queue 没有重复。
    • 我总是这样使用 hashset,它从来没有失败过哈哈。你有什么建议吗?
    • 如果没有使用引用链中的第一个引用,这将不起作用。因此:MainApplications 引用:ReferenceA --> ReferenceB --> ReferenceC 如果在 MainApplication 中使用 ReferenceA 中的类,则任何调用 GetReferencedAssemblies() 都不会返回 ReferenceA,这也不是 ReferenceB 或 ReferenceC .
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      如果您的程序集在编译时未引用任何代码,则这些程序集将不会作为对其他程序集的引用包含在内,即使您已将项目或 nuget 包添加为引用。这与DebugRelease 构建设置、代码优化等无关。在这些情况下,您必须显式调用Assembly.LoadFrom(dllFileName) 来加载程序集。

      【讨论】:

        【解决方案10】:

        在我的 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
        }
        

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-10-21
          • 2017-09-02
          • 1970-01-01
          • 2020-08-29
          • 1970-01-01
          • 1970-01-01
          • 2013-02-07
          相关资源
          最近更新 更多