Як ви пробираєтеся через завантажені в даний час збірки?


120

У моєму додатку ASP.NET у мене є сторінка "діагностики", яка виконує такі дії, як перевірка підключення до бази даних, відображення поточних програм та налаштувань 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навколо. Наразі це не є безпечним потоком за замовчуванням, оскільки він посилається на загальну статичну глобальну змінну, щоб зробити свою магію.


Я просто переписав це, щоб бути безпечним для потоків, тому його можна викликати з багатьох різних потоків одночасно (не впевнений, чому ви цього хотіли б, але ей, безпечніше). Повідомте мене, якщо ви хочете, щоб я опублікував код.
Контанго

2
@Contango Чи можете ви опублікувати безпечну версію для теми або, якщо ви написали блог про неї, опублікуйте це?
Роберт

2
Наївний спосіб зробити цю нитку безпечною - це вирішити lockвсю справу. Інший метод, який я застосував, усунув залежність від глобального статичного "_dependentAssemblyList", тому він стає безпечним для потоків, не потребуючи a lock, який має деякі незначні переваги у швидкості, якщо кілька потоків намагаються одночасно визначити, які збірки відсутні (це трохи кутовий корпус).
Контанго

3
додавання файлу lockне збирається додавати багато на шляху "потокової безпеки". Звичайно, що цей блок коду виконує лише один за одним; але інші потоки можуть завантажувати збірки в будь-який час, що їм подобається, і це може спричинити проблеми з деякими foreachпетлями.
Пітер Річі

1
@Peter Ritchie Існує загальна статична глобальна змінна, яка використовується під час рекурсії, тому додавання блокування навколо всіх доступу до цього зробить цю частину потоком безпечною. Це просто хороша практика програмування. Зазвичай всі необхідні збірки завантажуються при запуску, якщо не використовується щось на зразок MEF, тому безпека потоків насправді не є проблемою на практиці.
Контанго

193

Отримання завантажених збірок для поточного AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Отримання посилань на іншу збірку:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Зауважте, що якщо збірка A посилань, збірка B і збірка A завантажена, це не означає, що збірка B також завантажена. Збірка B завантажуватиметься лише тоді, коли і коли це буде потрібно. З цієї причини GetReferencedAssemblies()повертає AssemblyNameекземпляри, а не Assemblyекземпляри.


2
Ну, мені потрібно щось подібне. Враховуючи рішення .net, я хочу дізнатися всі посилання на всі проекти. Ідеї?
Кумар Вайбхав

Зверніть увагу, що обидва способи перелічують лише ті DLL, які фактично використовуються. Очевидно, немає сенсу мати посилання на рішення, які не використовуються, але це може бути заплутаним, коли хтось намагається спекулятивно просканувати ВСІ складання. Усі збори можуть просто не з’являтися.
Помпара

3
ОП запитує наразі завантажені збірки, на які не посилаються. Це відповідає на питання. Саме те, що я шукав.
MikeJansen

подія для того, щоб знати, коли завантажується збірка B?
Кікенет
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.