有没有一种方法可以强制将所有引用的程序集加载到应用程序域中?


80

我的项目是这样设置的:

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

项目“消费者”同时引用了“定义”和“实施”,但是没有静态引用“实施”中的任何类型。

当应用程序启动时,“消费者”项目将在“定义”中调用一个静态方法,该方法需要在“实施”中查找类型

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


1
它引起什么问题?为什么需要强制加载?
Mike 2'3

它根本没有加载,大概是因为没有静态依赖关系
Daniel Schaffer,2010年

您如何尝试在实现中“查找类型”?您是否正在寻找实现特定接口的东西?
麦克两

2
@迈克:是的。我正在做AppDomain.CurrentDomain.GetAssemblies,并使用linq查询对它们中的每一个递归调用GetTypes()。
Daniel Schaffer 2010年

Answers:


89

这似乎可以解决问题:

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


5
这对我不起作用,未加载的我引用的程序集未显示在AppDomain.CurrentDomain.GetAssemblies()中。嗯……
Ted

11
有什么设施?我没有通过搜索找到任何东西。
nuzzolilo 2014年

8
使用MEF,可以将以上代码简化为:(new DirectoryCatalog(".");需要参考System.ComponentModel.Composition)。
Allon Guralnek

1
这个答案解决了我的问题并为我工作。我有一个MS单元测试项目引用了我的另一个程序集,而AppDomain.CurrentDomain.GetAssemblies()在运行测试时未返回该程序集。我怀疑即使我的单元测试使用的是该库中的代码,该程序集也可能没有出现在“ GetAssemblies”中,因为与运行常规程序相比,vs.net加载MS单元测试项目(类库)的方式.exe应用程序。如果您的代码使用反射并且未通过单元测试,请记住一些注意事项。
Dean Lunz

3
只是要添加,请注意动态加载的程序集动态程序集不支持被调用的成员。筛选掉IsDynamic = false的程序集,或者如果您可以容忍负载,请尝试/捕获对CurrentDomain.Load的调用。和assembly.Location。那一个也需要检查。不适用于IsDynamic程序集。
Eli Gassert

63

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


我发现了,但是问题是我必须从被引用的程序集做任何事情……至少在单元测试的上下文中,GetCallingAssembly,GetExecutingAssembly当然会返回被引用的程序集,而GetEntryAssembly返回null :\
丹尼尔·谢弗

4
如果您要加载参考程序集,则以上内容将解决您的问题。您也可以询问特定类型的typeof(T).Assembly是否有帮助。我觉得您需要的是动态加载包含实现的程序集(未引用)。在这种情况下,您将必须保留一个静态名称列表并手动加载它们,或者遍历整个目录,然后使用正确的接口查找类型。
Fadrian Sudaman 2010年

1
@vanhelgen:根据我的经验,您几乎不需要明确地进行此操作。通常,CLR的“按需加载”效果很好。
乔恩·斯基特

2
在正常情况下,这可能是正确的,但是当使用DI容器(通过System.Reflection)发现可用服务时,它自然不会找到包含在尚未加载的程序集中的服务。从那时起,我的默认方法就是从应用程序的CompositionRoot中每个引用程序集的随机类型创建一个虚拟子类,以确保所有依赖项均已就绪。我希望我可以通过预先加载所有内容来跳过这些废话,即使是以进一步增加启动时间为代价。@JonSkeet还有另一种方法吗?thx
mfeineis 2015年

12
出现这种情况的地方是GetReferencedAssemblies显然返回了“优化的”列表-因此,如果您未在引用的程序集中显式调用代码,则不会将其包括在内。(根据本次讨论
FTWinston

23

只是想分享一个递归示例。我在启动例程中这样调用Lo​​adReferencedAssembly方法:

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

5
我想知道循环汇编引用是否会导致抛出堆栈溢出异常。
罗尼·奥弗比

1
罗尼,我不信,代码只运行的情况下,递归name是不是已经AppDomain.CurrentDomain.GetAssemblies(),这意味着如果它只会再次出现foreach回升AssemblyName还没有被加载。
Felype

1
O(n^2)对此算法的运行时间不满意(GetAssemblies().Any(...)foreach))。我会用aHashSet将其归纳为O(n)

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

小心告知该代码段应该放在哪里?
Telemat

1
我想象在您的引导加载程序/启动中。
Meirion Hughes 2014年

1
我可能是错的,但我认为您可以检查!y.IsDynamic一下.Where
Felype

1

鉴于今天我必须从特定路径加载程序集和依赖项,因此我编写了此类来实现。

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

1
好的代码,除了不需要字典,在这种情况下,一个简单的列表就足够了。我假设您的原始代码需要知道哪些程序集已加载,哪些程序集未加载,这就是为什么要使用Dictionary的原因。
Reinis

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程序包添加为引用,也不会将这些程序集作为对其他程序集的引用。这是不管DebugRelease构建设置,代码优化等。在这些情况下,您必须显式调用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.Interaction,程序集Microsoft.VisualBasic.dll中的方法(例如InputBox()etc)。

但是我的应用程序本身并未使用该程序集,因此该程序集永远不会加载。

因此,为了强制加载程序集,我最终将其简单地添加到了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
}

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.