Comment parcourez-vous les assemblys actuellement chargés?

120

J'ai une page "diagnostics" dans mon application ASP.NET qui fait des choses comme vérifier la (les) connexion (s) à la base de données, afficher les appSettings et ConnectionStrings actuels, etc. Une section de cette page affiche les versions d'assembly des types importants utilisés tout au long , mais je ne pouvais pas comprendre comment afficher efficacement les versions de TOUS les assemblys chargés.

Quel est le moyen le plus efficace de déterminer tous les assemblys actuellement référencés et / ou chargés dans une application .NET?

Remarque: je ne suis pas intéressé par les méthodes basées sur des fichiers, comme l'itération via * .dll dans un répertoire particulier. Je suis intéressé par ce que l'application utilise actuellement.

Jess Chadwick
la source

Réponses:

24

Cette méthode d'extension obtient tous les assemblys référencés, de manière récursive, y compris les assemblys imbriqués.

Lors de son utilisation ReflectionOnlyLoad, il charge les assemblys dans un AppDomain séparé, ce qui présente l'avantage de ne pas interférer avec le processus JIT.

Vous remarquerez qu'il existe également un fichier MyGetMissingAssembliesRecursive. Vous pouvez l'utiliser pour détecter les assemblys manquants qui sont référencés, mais qui ne sont pas présents dans le répertoire actuel pour une raison quelconque. Ceci est incroyablement utile lors de l'utilisation de MEF . La liste de retour vous donnera à la fois l'assembly manquant et à qui il appartient (son parent).

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

Mettre à jour

Pour rendre ce thread de code sûr, mettez un lockautour de lui. Il n'est actuellement pas thread-safe par défaut, car il fait référence à une variable globale statique partagée pour faire sa magie.

Contango
la source
Je viens de réécrire ceci pour être thread-safe, afin qu'il puisse être appelé à partir de nombreux threads différents simultanément (je ne sais pas pourquoi vous voudriez cela, mais bon, c'est plus sûr). Faites-moi savoir si vous souhaitez que je poste le code.
Contango
2
@Contango Pourriez-vous publier votre version sécurisée de Thread, ou si vous avez écrit un blog à ce sujet, publier cela?
Robert
2
La manière naïve de rendre ce fil sûr est de mettre un lockautour de tout. L'autre méthode que j'ai utilisée a éliminé la dépendance sur la statique globale "_dependentAssemblyList", elle devient donc thread-safe sans avoir besoin d'un lock, ce qui présente de légers avantages en termes de vitesse si plusieurs threads essaient de déterminer simultanément les assemblys manquants (c'est un peu une valise d'angle).
Contango
3
l'ajout d'un lockn'ajoutera pas grand-chose en termes de "thread safe". Bien sûr, cela fait que ce bloc de code ne s'exécute qu'un à la fois; mais d'autres threads peuvent charger des assemblys à tout moment et cela pourrait causer des problèmes avec certaines des foreachboucles.
Peter Ritchie
1
@Peter Ritchie Il existe une variable globale statique partagée qui est utilisée pendant la récursivité, donc l'ajout d'un verrou autour de tous les accès rendra cette partie sûre pour les threads. C'est juste une bonne pratique de programmation. Habituellement, tous les assemblys requis sont chargés au démarrage, sauf si quelque chose comme MEF est utilisé, donc la sécurité des threads n'est pas vraiment un problème en pratique.
Contango
193

Obtenir des assemblys chargés pour le courant AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Obtenir les assemblys référencés par un autre assembly:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Notez que si l'assemblage A fait référence à l'assemblage B et l'assemblage A est chargé, cela n'implique pas que l'assemblage B est également chargé. L'assemblage B ne sera chargé que si et quand il est nécessaire. Pour cette raison, GetReferencedAssemblies()renvoie des AssemblyNameinstances plutôt que des Assemblyinstances.

Kent Boogaart
la source
2
Eh bien, j'ai besoin de quelque chose comme ça - étant donné une solution .net, je veux découvrir tous les assemblys référencés dans tous les projets. Des idées?
Kumar Vaibhav
Veuillez noter que les deux méthodes ne répertorient que les DLL qui sont réellement utilisées. Évidemment, cela n'a aucun sens d'avoir des références dans des solutions qui ne sont pas utilisées, mais cela peut être déroutant lorsque quelqu'un essaie de parcourir de manière spéculative TOUS les assemblys. Toutes les assemblées peuvent simplement ne pas apparaître.
Pompair
3
OP demande les assemblys actuellement chargés non référencés. Cela répond à la question. Exactement ce que je cherchais.
MikeJansen
événement pour savoir quand l'assemblage B est chargé?
Kiquenet