如何遍历当前加载的程序集?


120

我在ASP.NET应用程序中有一个“诊断”页面,该页面执行诸如验证数据库连接,显示当前appSettings和ConnectionStrings等操作。此页面的一部分显示了在整个过程中使用的重要类型的Assembly版本,但我不知道如何有效显示所有已加载程序集的版本。

找出.NET应用程序中所有当前引用和/或加载的程序集的最有效方法是什么?

注意:我对基于文件的方法不感兴趣,例如遍历特定目录中的* .dll。我对应用程序当前实际使用的内容感兴趣。

Answers:


24

此扩展方法以递归方式获取所有引用的程序集,包括嵌套程序集。

使用时ReflectionOnlyLoad,它将程序集加载到单独的AppDomain中,其优点是不干扰JIT流程。

您会注意到还有一个MyGetMissingAssembliesRecursive。您可以使用它来检测由于某些原因而被引用但在当前目录中不存在的所有缺少的程序集。使用MEF时,这非常有用。返回列表将为您提供丢失的程序集以及拥有它的人(它的父程序)。

/// <summary>
///     Intent: Get referenced assemblies, either recursively or flat. Not thread safe, if running in a multi
///     threaded environment must use locks.
/// </summary>
public static class GetReferencedAssemblies
{
    static void Demo()
    {
        var referencedAssemblies = Assembly.GetEntryAssembly().MyGetReferencedAssembliesRecursive();
        var missingAssemblies = Assembly.GetEntryAssembly().MyGetMissingAssembliesRecursive();
        // Can use this within a class.
        //var referencedAssemblies = this.MyGetReferencedAssembliesRecursive();
    }

    public class MissingAssembly
    {
        public MissingAssembly(string missingAssemblyName, string missingAssemblyNameParent)
        {
            MissingAssemblyName = missingAssemblyName;
            MissingAssemblyNameParent = missingAssemblyNameParent;
        }

        public string MissingAssemblyName { get; set; }
        public string MissingAssemblyNameParent { get; set; }
    }

    private static Dictionary<string, Assembly> _dependentAssemblyList;
    private static List<MissingAssembly> _missingAssemblyList;

    /// <summary>
    ///     Intent: Get assemblies referenced by entry assembly. Not recursive.
    /// </summary>
    public static List<string> MyGetReferencedAssembliesFlat(this Type type)
    {
        var results = type.Assembly.GetReferencedAssemblies();
        return results.Select(o => o.FullName).OrderBy(o => o).ToList();
    }

    /// <summary>
    ///     Intent: Get assemblies currently dependent on entry assembly. Recursive.
    /// </summary>
    public static Dictionary<string, Assembly> MyGetReferencedAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();

        InternalGetDependentAssembliesRecursive(assembly);

        // Only include assemblies that we wrote ourselves (ignore ones from GAC).
        var keysToRemove = _dependentAssemblyList.Values.Where(
            o => o.GlobalAssemblyCache == true).ToList();

        foreach (var k in keysToRemove)
        {
            _dependentAssemblyList.Remove(k.FullName.MyToName());
        }

        return _dependentAssemblyList;
    }

    /// <summary>
    ///     Intent: Get missing assemblies.
    /// </summary>
    public static List<MissingAssembly> MyGetMissingAssembliesRecursive(this Assembly assembly)
    {
        _dependentAssemblyList = new Dictionary<string, Assembly>();
        _missingAssemblyList = new List<MissingAssembly>();
        InternalGetDependentAssembliesRecursive(assembly);

        return _missingAssemblyList;
    }

    /// <summary>
    ///     Intent: Internal recursive class to get all dependent assemblies, and all dependent assemblies of
    ///     dependent assemblies, etc.
    /// </summary>
    private static void InternalGetDependentAssembliesRecursive(Assembly assembly)
    {
        // Load assemblies with newest versions first. Omitting the ordering results in false positives on
        // _missingAssemblyList.
        var referencedAssemblies = assembly.GetReferencedAssemblies()
            .OrderByDescending(o => o.Version);

        foreach (var r in referencedAssemblies)
        {
            if (String.IsNullOrEmpty(assembly.FullName))
            {
                continue;
            }

            if (_dependentAssemblyList.ContainsKey(r.FullName.MyToName()) == false)
            {
                try
                {
                    var a = Assembly.ReflectionOnlyLoad(r.FullName);
                    _dependentAssemblyList[a.FullName.MyToName()] = a;
                    InternalGetDependentAssembliesRecursive(a);
                }
                catch (Exception ex)
                {
                    _missingAssemblyList.Add(new MissingAssembly(r.FullName.Split(',')[0], assembly.FullName.MyToName()));
                }
            }
        }
    }

    private static string MyToName(this string fullName)
    {
        return fullName.Split(',')[0];
    }
}

更新资料

为了使此代码线程安全,请在其lock周围放一个。默认情况下,它当前不是线程安全的,因为它引用共享的静态全局变量来发挥作用。


我只是将其重写为线程安全的,因此可以同时从许多不同的线程中调用它(不确定为什么会这样,但是,嘿,它更安全)。让我知道是否要我发布代码。
康坦戈2014年

2
@Contango您可以发布您的Thread安全版本,或者如果您撰写了有关该版本的博客,请发布该版本吗?
罗伯特

2
使该线程安全的幼稚方法是将所有内容放在一起lock。我使用的另一种方法消除了对全局静态“ _dependentAssemblyList”的依赖,因此不需要a即可成为线程安全的lock,如果多个线程试图同时确定缺少哪些程序集,则它具有一些速度上的优势(这有点角落案例)。
Contango

3
添加a lock不会以“线程安全”的方式增加太多。当然,这使得该代码块一次只能执行一次;但是其他线程可以在需要时随时加载程序集,这可能会导致某些foreach循环出现问题。
彼得·里奇

1
@Peter Ritchie在递归过程中使用了一个共享的静态全局变量,因此在对该变量的所有访问周围添加锁将使该部分线程安全。这只是良好的编程习惯。通常,所有必需的程序集都在启动时加载,除非使用了MEF之类的东西,所以实际上线程安全并不是真正的问题。
Contango

193

获取当前的装配件AppDomain

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

获取另一个程序集引用的程序集:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

请注意,如果程序集A引用了程序集B,而程序集A已加载,则并不意味着程序集B也已加载。仅在需要时以及在需要时才加载程序集B。因此,GetReferencedAssemblies()返回AssemblyName实例而不是Assembly实例。


2
好吧,我需要类似的东西-给定一个.net解决方案,我想找出所有项目中所有引用的程序集。有想法吗?
Kumar Vaibhav

请注意,这两种方法仅列出实际使用的dll。显然,在未使用的解决方案中具有引用是没有意义的,但是当有人尝试通过推测方式扫描所有程序集时,这可能会造成混淆。所有程序集可能只是不显示。
Pompair

3
OP询问当前加载的程序集而不是引用的程序集。这回答了问题。正是我想要的。
MikeJansen

知道何时加载程序集B的事件?
Kiquenet,
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.