Como você percorre os assemblies atualmente carregados?


120

Eu tenho uma página de "diagnóstico" no meu aplicativo ASP.NET que faz coisas como verificar a (s) conexão (s) do banco de dados, exibir o appSettings e ConnectionStrings etc. etc. Uma seção desta página exibe as versões do Assembly dos tipos importantes usados ​​em , mas não consegui descobrir como mostrar efetivamente as versões de TODAS as montagens carregadas.

Qual é a maneira mais eficaz de descobrir todos os assemblies atualmente referenciados e / ou carregados em um aplicativo .NET?

Nota: Não estou interessado em métodos baseados em arquivo, como iterar através de * .dll em um diretório específico. Estou interessado no que o aplicativo está realmente usando no momento.

Respostas:


24

Esse método de extensão obtém todos os assemblies referenciados, recursivamente, incluindo assemblies aninhados.

Como ele usa ReflectionOnlyLoad, ele carrega os assemblies em um AppDomain separado, que tem a vantagem de não interferir no processo JIT.

Você notará que também existe um MyGetMissingAssembliesRecursive. Você pode usar isso para detectar quaisquer assemblies ausentes que são referenciados, mas não estão presentes no diretório atual por algum motivo. Isso é incrivelmente útil ao usar o MEF . A lista de retorno fornecerá a você a montagem ausente e quem a possui (seu pai).

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

Atualizar

Para tornar esse segmento de código seguro, coloque-o em lockvolta. No momento, não é seguro para threads por padrão, pois faz referência a uma variável global estática compartilhada para fazer sua mágica.


Eu apenas reescrevi isso para ser seguro para threads, para que possa ser chamado de muitos threads diferentes simultaneamente (não sei por que você iria querer isso, mas ei, é mais seguro). Entre em contato se você quiser que eu publique o código.
Contango 27/10/14

2
@Contango Você poderia postar sua versão segura do Thread ou, se você escreveu um blog sobre isso, postá-la?
Robert

2
A maneira ingênua de tornar esse segmento seguro é colocar tudo em prática lock. O outro método que eu usei eliminou a dependência da estática global "_dependentAssemblyList", tornando-a segura para threads sem a necessidade de um lock, o que tem algumas vantagens de velocidade se vários threads estiverem tentando determinar simultaneamente quais montagens estão ausentes (isso é um pouco de uma caixa de canto).
Contango 12/03/2015

3
adicionar um locknão adicionará muito em termos de "thread safe". Claro, isso faz com que esse bloco de código seja executado apenas um de cada vez; mas outros encadeamentos podem carregar montagens a qualquer momento, o que pode causar problemas em alguns dos foreachloops.
Peter Ritchie

1
@ Peter Ritchie Há uma variável global estática compartilhada que é usada durante a recursão, portanto, adicionar uma trava em torno de todos os acessos fará com que o segmento da parte seja seguro. É apenas uma boa prática de programação. Normalmente, todos os conjuntos necessários são carregados na inicialização, a menos que algo como MEF seja usado, portanto a segurança do encadeamento não é realmente um problema na prática.
Contango 14/03

193

Obtendo montagens carregadas para o atual AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Obtendo as montagens referenciadas por outra montagem:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Observe que, se a montagem A referenciar a montagem B e a montagem A estiver carregada, isso não implica que a montagem B também esteja carregada. O conjunto B será carregado apenas se e quando for necessário. Por esse motivo, GetReferencedAssemblies()retorna AssemblyNameinstâncias em vez de Assemblyinstâncias.


2
Bem, eu preciso de algo assim - dada uma solução .net, quero descobrir todos os assemblies referenciados em todos os projetos. Ideias?
Kumar Vaibhav

Observe que ambos os métodos listam apenas dlls que são realmente usadas. Obviamente, não faz sentido ter referências em soluções que não são usadas, mas isso pode ser confuso quando alguém está tentando varrer especulativamente TODAS as montagens. Todas as montagens podem simplesmente não aparecer.
Pompair

3
O OP solicita assemblies atualmente carregados e não referenciados. Isso responde à pergunta. Exatamente o que eu estava procurando.
25816 MikeJansen

evento para saber quando o Conjunto B é carregado?
Kiquenet 8/08/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.