Wie durchlaufen Sie aktuell geladene Assemblys?


120

In meiner ASP.NET-Anwendung befindet sich eine "Diagnoseseite", auf der beispielsweise die Datenbankverbindung (en) überprüft, die aktuellen AppSettings und ConnectionStrings usw. angezeigt werden. In einem Abschnitt dieser Seite werden die Assembly-Versionen wichtiger Typen angezeigt, die durchgehend verwendet werden , aber ich konnte nicht herausfinden, wie die Versionen ALLER geladenen Assemblys effektiv angezeigt werden können.

Was ist der effektivste Weg, um alle aktuell referenzierten und / oder geladenen Assemblies in einer .NET-Anwendung herauszufinden ?

Hinweis: Ich interessiere mich nicht für dateibasierte Methoden wie das Durchlaufen von * .dll in einem bestimmten Verzeichnis. Ich bin daran interessiert, was die Anwendung gerade tatsächlich verwendet .

Antworten:


24

Diese Erweiterungsmethode ruft rekursiv alle referenzierten Assemblys ab, einschließlich verschachtelter Assemblys.

Während der Verwendung ReflectionOnlyLoadwerden die Assemblys in eine separate AppDomain geladen, was den Vorteil hat, dass der JIT-Prozess nicht beeinträchtigt wird.

Sie werden feststellen, dass es auch eine gibt MyGetMissingAssembliesRecursive. Sie können dies verwenden, um fehlende Assemblys zu erkennen, auf die verwiesen wird, die jedoch aus irgendeinem Grund nicht im aktuellen Verzeichnis vorhanden sind. Dies ist unglaublich nützlich, wenn Sie MEF verwenden . In der Rückgabeliste finden Sie sowohl die fehlende Assembly als auch den Eigentümer (die übergeordnete Assembly).

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

Aktualisieren

Um diesen Code-Thread sicher zu machen, setzen Sie ein lockum ihn herum. Es ist derzeit standardmäßig nicht threadsicher, da es auf eine gemeinsam genutzte statische globale Variable verweist, um ihre Magie zu entfalten.


Ich habe dies nur umgeschrieben, um Thread-sicher zu sein, damit es von vielen verschiedenen Threads gleichzeitig aufgerufen werden kann (nicht sicher, warum Sie das wollen würden, aber hey, es ist sicherer). Lassen Sie mich wissen, ob ich den Code posten soll.
Contango

2
@Contango Könnten Sie Ihre Thread-sichere Version veröffentlichen oder, wenn Sie einen Blog darüber geschrieben haben, diese veröffentlichen?
Robert

2
Der naive Weg, um diesen Thread sicher zu machen, besteht darin, lockdas Ganze zu umgehen. Die andere Methode, die ich verwendet habe, hat die Abhängigkeit von der globalen statischen "_dependentAssemblyList" beseitigt, sodass sie threadsicher wird, ohne dass a erforderlich ist. Dies lockhat einige leichte Geschwindigkeitsvorteile, wenn mehrere Threads gleichzeitig versuchen, festzustellen, welche Assemblys fehlen (dies ist ein bisschen) ein Eckfall).
Contango

3
Das Hinzufügen von a lockwird nicht viel in Bezug auf "thread safe" hinzufügen. Sicher, das führt dazu, dass dieser Codeblock jeweils nur einen ausführt. Andere Threads können Assemblys jedoch jederzeit laden, was zu Problemen mit einigen foreachSchleifen führen kann.
Peter Ritchie

1
@Peter Ritchie Es gibt eine gemeinsam genutzte statische globale Variable, die während der Rekursion verwendet wird. Wenn Sie also eine Sperre für alle Zugriffe hinzufügen, wird dieser Teil-Thread sicher. Es ist nur eine gute Programmierpraxis. Normalerweise werden alle erforderlichen Assemblys beim Start geladen, es sei denn, es wird MEF verwendet, sodass die Thread-Sicherheit in der Praxis kein wirkliches Problem darstellt.
Contango

193

Laden geladener Assemblys für den aktuellen AppDomain:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

Abrufen der Assemblys, auf die von einer anderen Assembly verwiesen wird:

var referencedAssemblies = someAssembly.GetReferencedAssemblies();

Beachten Sie, dass, wenn Baugruppe A auf Baugruppe B verweist und Baugruppe A geladen ist, dies nicht bedeutet, dass Baugruppe B ebenfalls geladen ist. Baugruppe B wird nur geladen, wenn dies erforderlich ist. Aus diesem Grund GetReferencedAssemblies()gibt AssemblyNameInstanzen anstatt AssemblyInstanzen.


2
Nun, ich brauche so etwas - angesichts einer .net-Lösung möchte ich alle referenzierten Assemblys in allen Projekten herausfinden. Ideen?
Kumar Vaibhav

Bitte beachten Sie, dass beide Methoden nur DLLs auflisten, die tatsächlich verwendet werden. Natürlich macht es keinen Sinn, Referenzen in Lösungen zu haben, die nicht verwendet werden. Dies kann jedoch verwirrend sein, wenn jemand versucht, ALLE Assemblys spekulativ zu durchsuchen. Möglicherweise werden nicht alle Baugruppen angezeigt.
Pompair

3
OP fragt aktuell geladene Assemblys, auf die nicht verwiesen wird. Dies beantwortet die Frage. Genau das, wonach ich gesucht habe.
MikeJansen

Ereignis für wissen, wann Baugruppe B geladen wird?
Kiquenet
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.