¿Cómo recorre los ensamblajes cargados actualmente?


120

Tengo una página de "diagnósticos" en mi aplicación ASP.NET que hace cosas como verificar la (s) conexión (es) de la base de datos, mostrar la configuración actual de la aplicación y las cadenas de conexión, etc. Una sección de esta página muestra las versiones de ensamblaje de los tipos importantes utilizados en todo , pero no pude averiguar cómo mostrar efectivamente las versiones de TODOS los ensamblajes cargados.

¿Cuál es la forma más eficaz de averiguar todos los ensamblados cargados o referenciados actualmente en una aplicación .NET?

Nota: No estoy interesado en métodos basados ​​en archivos, como iterar a través de * .dll en un directorio en particular. Estoy interesado en lo que la aplicación está usando en este momento.

Respuestas:


24

Este método de extensión obtiene todos los ensamblados referenciados, de forma recursiva, incluidos los ensamblados anidados.

A medida que se usa ReflectionOnlyLoad, carga los ensamblados en un AppDomain separado, lo que tiene la ventaja de no interferir con el proceso JIT.

Notarás que también hay un MyGetMissingAssembliesRecursive. Puede usar esto para detectar los ensamblados faltantes a los que se hace referencia, pero que no están presentes en el directorio actual por alguna razón. Esto es increíblemente útil cuando se usa MEF . La lista de devolución le dará tanto el ensamblado que falta como quién es el propietario (su padre).

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

Actualizar

Para que este subproceso de código sea seguro, coloque un lock. Actualmente no es seguro para subprocesos de forma predeterminada, ya que hace referencia a una variable global estática compartida para hacer su magia.


Acabo de reescribir esto para que sea seguro para subprocesos, por lo que se puede llamar desde muchos subprocesos diferentes simultáneamente (no estoy seguro de por qué querrías eso, pero bueno, es más seguro). Avísame si quieres que publique el código.
Contango

2
@Contango ¿Podría publicar su versión segura para Thread, o si ha escrito un blog al respecto, publicarla?
Robert

2
La forma ingenua de hacer que este hilo sea seguro es rodearlo locktodo. El otro método que utilicé eliminó la dependencia de la estática global "_dependentAssemblyList", por lo que se vuelve seguro para subprocesos sin necesidad de un lock, lo que tiene algunas ventajas de velocidad leves si varios subprocesos intentan determinar simultáneamente qué ensamblajes faltan (esto es un poco de un estuche de esquina).
Contango

3
agregar un lockno va a agregar mucho en cuanto a "seguridad para subprocesos". Claro, eso hace que ese bloque de código se ejecute solo uno a la vez; pero otros subprocesos pueden cargar ensamblajes en cualquier momento que deseen y eso podría causar problemas con algunos de los foreachbucles.
Peter Ritchie

1
@Peter Ritchie Hay una variable global estática compartida que se usa durante la recursividad, por lo que agregar un bloqueo alrededor de todos los accesos hará que esa parte sea segura para el hilo. Es una buena práctica de programación. Por lo general, todos los ensamblajes necesarios se cargan al inicio, a menos que se use algo como MEF, por lo que la seguridad de los subprocesos no es realmente un problema en la práctica.
Contango

193

Obteniendo ensamblajes cargados para la corriente AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Obtener los ensamblados a los que hace referencia otro ensamblado:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Tenga en cuenta que si el ensamblado A hace referencia al ensamblaje B y el ensamblaje A está cargado, eso no implica que el ensamblaje B también esté cargado. El ensamblaje B solo se cargará cuando sea necesario. Por esa razón, GetReferencedAssemblies()devuelve AssemblyNameinstancias en lugar de Assemblyinstancias.


2
Bueno, necesito algo como esto: dada una solución .net, quiero averiguar todos los ensamblados a los que se hace referencia en todos los proyectos. Ideas?
Kumar Vaibhav

Tenga en cuenta que ambos métodos solo enumeran las DLL que se utilizan realmente. Obviamente, no tiene sentido tener referencias en soluciones que no se utilizan, pero esto puede resultar confuso cuando alguien está tratando de escanear especulativamente TODOS los ensamblados. Es posible que no aparezcan todas las asambleas.
Pompair

3
OP pregunta ensamblajes cargados actualmente no ensamblados referenciados. Esto responde a la pregunta. Exactamente lo que estaba buscando.
MikeJansen

evento para saber cuándo se carga el Ensamblaje B?
Kiquenet
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.