简体   繁体   English

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

[英]Is there a way to force all referenced assemblies to be loaded into the app domain?

My projects are set up like this:我的项目是这样设置的:

  • Project "Definition" “定义”项目
  • Project "Implementation"项目实施”
  • Project "Consumer" “消费者”项目

Project "Consumer" references both "Definition" and "Implementation", but does not statically reference any types in "Implementation".项目“Consumer”同时引用“Definition”和“Implementation”,但不静态引用“Implementation”中的任何类型。

When the application starts, Project "Consumer" calls a static method in "Definition", which needs to find types in "Implementation"应用程序启动时,项目“Consumer”调用“Definition”中的静态方法,需要在“Implementation”中查找类型

Is there a way I can force any referenced assembly to be loaded into the App Domain without knowing the path or name, and preferably without having to use a full-fledged IOC framework?有没有一种方法可以在不知道路径或名称的情况下强制将任何引用的程序集加载到应用程序域中,最好不必使用成熟的 IOC 框架?

This seemed to do the trick:这似乎可以解决问题:

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

As Jon noted, the ideal solution would need to recurse into the dependencies for each of the loaded assemblies, but in my specific scenario I don't have to worry about it.正如 Jon 所指出的,理想的解决方案需要递归到每个加载的程序集的依赖项中,但在我的特定场景中,我不必担心它。


Update: The Managed Extensibility Framework (System.ComponentModel) included in .NET 4 has much better facilities for accomplishing things like this.更新: .NET 4 中包含的托管扩展性框架 (System.ComponentModel) 有更好的工具来完成这样的事情。

You can use Assembly.GetReferencedAssemblies to get an AssemblyName[] , and then call Assembly.Load(AssemblyName) on each of them.您可以使用Assembly.GetReferencedAssemblies来获取AssemblyName[] ,然后对它们中的每一个调用Assembly.Load(AssemblyName) You'll need to recurse, of course - but preferably keeping track of assemblies you've already loaded :)当然,您需要递归 - 但最好跟踪您已经加载的程序集:)

just wanted to share a recursive example.只是想分享一个递归的例子。 I'm calling the LoadReferencedAssembly method in my startup routine like this:我在启动例程中调用 LoadReferencedAssembly 方法,如下所示:

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    this.LoadReferencedAssembly(assembly);
}

This is the recursive method:这是递归方法:

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

If you use Fody.Costura, or any other assembly merging solution, the accepted answer will not work.如果您使用 Fody.Costura 或任何其他程序集合并解决方案,则接受的答案将不起作用。

The following loads the Referenced Assemblies of any currently loaded Assembly.以下加载任何当前加载的程序集的引用程序集。 Recursion is left to you.递归留给你。

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

Seeing as I had to load an assembly + dependencies from a specific path today I wrote this class to do it.鉴于我今天必须从特定路径加载程序集 + 依赖项,我编写了这个类来完成它。

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

For getting referenced assembly by name you can use following method:要按名称获取引用的程序集,您可以使用以下方法:

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

I created my own based on @Jon Skeet answer with name prefix filtering to avoid loading unnecessary assemblies:我根据@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;
}

Yet another version (based on Daniel Schaffer answer) is the case when you might not need to load all Assemblies, but a predefined number of them:另一个版本(基于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 you have assemblies where no code is referenced at compile time, those assemblies will not be included as a reference to your other assembly, even if you have added the project or nuget package as a reference.如果您的程序集在编译时未引用任何代码,则这些程序集不会作为对其他程序集的引用包含在内,即使您已添加项目或 nuget 包作为引用。 This is regardless of Debug or Release build settings, code optimization, etc. In these cases, you must explicitly call Assembly.LoadFrom(dllFileName) to get the assembly loaded.这与DebugRelease构建设置、代码优化等无关。在这些情况下,您必须显式调用Assembly.LoadFrom(dllFileName)以加载程序集。

In my winforms application I give JavaScript (in a WebView2 control) the possibility to call various .NET things, for example methods of Microsoft.VisualBasic.Interaction in the assembly Microsoft.VisualBasic.dll (such as InputBox() etc).在我的 winforms 应用程序中,我为 JavaScript(在 WebView2 控件中)提供了调用各种 .NET 事物的可能性,例如Microsoft.VisualBasic.Interaction集中的 Microsoft.VisualBasic.Interaction 方法(例如InputBox()等)。

But my application as such does not use that assembly, so the assembly is never loaded.但是我的应用程序本身不使用该程序集,因此永远不会加载该程序集。

So to force the assembly to load, I ended up simply adding this in my Form1_Load:所以为了强制加载程序集,我最终只是在我的 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
}

The compiler thinks that the assembly might be needed, but in reality this never happens of course.编译器认为可能需要程序集,但实际上这当然不会发生。

Not a very sophisticated solution, but quick and dirty.不是一个非常复杂的解决方案,但又快又脏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM