Как вы просматриваете загруженные в данный момент сборки?

120

У меня есть страница «диагностики» в моем приложении ASP.NET, которая выполняет такие действия, как проверка подключения (-ий) к базе данных, отображение текущих appSettings и ConnectionStrings и т. Д. В разделе этой страницы отображаются версии сборки важных типов, используемых повсюду , но я не мог понять, как эффективно показывать версии ВСЕХ загруженных сборок.

Каков наиболее эффективный способ выяснить все используемые в настоящее время и / или загруженные сборки в приложении .NET?

Примечание. Меня не интересуют файловые методы, такие как итерация по * .dll в определенном каталоге. Мне интересно, что приложение сейчас использует на самом деле .

Джесс Чедвик
источник

Ответы:

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его. В настоящее время он не является потокобезопасным по умолчанию, так как он ссылается на общую статическую глобальную переменную, чтобы творить чудеса.

Контанго
источник
Я просто переписал это, чтобы обеспечить потокобезопасность, чтобы его можно было вызывать одновременно из многих разных потоков (не уверен, зачем вам это нужно, но, эй, это безопаснее). Дайте мне знать, если вы хотите, чтобы я опубликовал код.
Contango
2
@Contango Не могли бы вы опубликовать свою безопасную версию Thread, или, если вы написали об этом в блоге, опубликовать ее?
Роберт
2
Наивный способ сделать этот потокобезопасным - поставить lockвокруг всего этого. Другой метод, который я использовал, устранил зависимость от глобального статического "_dependentAssemblyList", поэтому он стал потокобезопасным без необходимости lock, что дает некоторые небольшие преимущества в скорости, если несколько потоков пытаются одновременно определить, какие сборки отсутствуют (это немного угловой корпус).
Contango,
3
добавление 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, я хочу узнать все сборки, на которые есть ссылки, во всех проектах. Идеи?
Кумар Вайбхав 09
Обратите внимание, что оба метода перечисляют только фактически используемые библиотеки DLL. Очевидно, нет смысла иметь ссылки на решения, которые не используются, но это может сбивать с толку, когда кто-то пытается спекулятивно сканировать ВСЕ сборки. Все сборки могут просто не отображаться.
Pompair
3
OP запрашивает загруженные в данный момент сборки, на которые нет ссылок. Это отвечает на вопрос. Именно то, что я искал.
MikeJansen
событие, чтобы узнать, когда сборка B загружена?
Kiquenet 08