So verhindern Sie ReflectionTypeLoadException beim Aufruf von Assembly.GetTypes ()

97

Ich versuche, eine Assembly nach Typen zu scannen, die eine bestimmte Schnittstelle implementieren, indem ich folgenden Code verwende:

public List<Type> FindTypesImplementing<T>(string assemblyPath)
{
    var matchingTypes = new List<Type>();
    var asm = Assembly.LoadFrom(assemblyPath);
    foreach (var t in asm.GetTypes())
    {
        if (typeof(T).IsAssignableFrom(t))
            matchingTypes.Add(t);
    }
    return matchingTypes;
}

Mein Problem ist, dass ich in einigen Fällen ReflectionTypeLoadExceptionbeim Aufruf eine bekomme asm.GetTypes(), z. B. wenn die Assembly Typen enthält, die auf eine Assembly verweisen, die derzeit nicht verfügbar ist.

In meinem Fall interessieren mich die Typen, die das Problem verursachen, nicht. Die Typen, nach denen ich suche, benötigen keine nicht verfügbaren Assemblys.

Die Frage ist: Ist es möglich, die Typen, die die Ausnahme verursachen, irgendwie zu überspringen / zu ignorieren, aber dennoch die anderen in der Assembly enthaltenen Typen zu verarbeiten?

M4N
quelle
1
Es ist vielleicht viel mehr ein Umschreiben als das, wonach Sie suchen, aber MEF bietet Ihnen ähnliche Funktionen. Markieren Sie einfach jede Ihrer Klassen mit einem [Export] -Tag, das die implementierte Schnittstelle angibt. Dann können Sie nur die Schnittstellen importieren, an denen Sie gerade interessiert sind.
Dirk Dastardly
@ Draw, Danke für deinen Kommentar. Ich dachte über die Verwendung von MEF nach, wollte aber sehen, ob es eine andere, billigere Lösung gibt.
M4N
Es ist eine einfache Problemumgehung, der Plugin-Klassenfactory einen bekannten Namen zu geben, damit Sie Activator.CreateInstance () direkt verwenden können. Wenn Sie diese Ausnahme jetzt aufgrund eines Problems mit der Assemblyauflösung erhalten, erhalten Sie sie wahrscheinlich auch später.
Hans Passant
1
@ Hans: Ich bin nicht sicher, ob ich das vollständig verstehe. Die Assembly, die ich scanne, enthält möglicherweise eine beliebige Anzahl von Typen, die die angegebene Schnittstelle implementieren. Es gibt also keinen bekannten Typ. (und auch: Ich
scanne
2
Ich habe fast den gleichen Code und das gleiche Problem. Und die Baugruppe, die ich erforsche, ist gegeben von AppDomain.CurrentDomain.GetAssemblies(), dies funktioniert auf meiner Maschine, aber nicht auf anderen Maschinen. Warum zum Teufel sollten einige Assemblys aus meiner ausführbaren Datei sowieso nicht lesbar / geladen sein?
v.oddou

Antworten:

130

Ein ziemlich böser Weg wäre:

Type[] types;
try
{
    types = asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
    types = e.Types;
}
foreach (var t in types.Where(t => t != null))
{
    ...
}

Es ist definitiv ärgerlich, dies tun zu müssen. Sie können eine Erweiterungsmethode verwenden, um es im "Client" -Code schöner zu machen:

public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
    // TODO: Argument validation
    try
    {
        return assembly.GetTypes();
    }
    catch (ReflectionTypeLoadException e)
    {
        return e.Types.Where(t => t != null);
    }
}

Sie können auch wünschen , die bewegen returnAnweisung aus dem catch - Block - ich bin schrecklich nicht so scharf auf sie selbst dort zu sein, aber es wahrscheinlich ist der kürzeste Code ...

Jon Skeet
quelle
2
Danke, das scheint eine Lösung zu sein (und ich stimme zu, es scheint keine saubere Lösung zu sein).
M4N
4
Diese Lösung weist weiterhin Probleme auf, wenn Sie versuchen, die Liste der in der Ausnahme offengelegten Typen zu verwenden. Unabhängig vom Grund für die Ausnahme beim Laden des Typs wird FileNotFound, BadImage usw. weiterhin auf jeden Zugriff auf die fraglichen Typen zugreifen.
Sweetfa
@sweetfa: Ja, es ist sehr begrenzt - aber wenn das OP zum Beispiel nur die Namen finden muss, sollte es in Ordnung sein.
Jon Skeet
1
Witzig, dieser Beitrag wird hier zitiert, ziemlich interessant: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa Dies ist, was ich tue, um das Problem der FileNotFound- Ausnahme bei den zurückgegebenen Typen zu vermeiden : From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)Es scheint großartig zu funktionieren.
ElektroStudios
22

Obwohl es den Anschein hat, dass irgendwann nichts mehr getan werden kann, ohne die ReflectionTypeLoadException zu erhalten, sind die obigen Antworten dahingehend begrenzt, dass jeder Versuch, die aus der Ausnahme bereitgestellten Typen zu verwenden, weiterhin Probleme mit dem ursprünglichen Problem verursacht, das dazu geführt hat, dass der Typ nicht geladen werden konnte.

Um dies zu überwinden, beschränkt der folgende Code die Typen auf diejenigen, die sich in der Assembly befinden, und ermöglicht es einem Prädikat, die Liste der Typen weiter einzuschränken.

    /// <summary>
    /// Get the types within the assembly that match the predicate.
    /// <para>for example, to get all types within a namespace</para>
    /// <para>    typeof(SomeClassInAssemblyYouWant).Assembly.GetMatchingTypesInAssembly(item => "MyNamespace".Equals(item.Namespace))</para>
    /// </summary>
    /// <param name="assembly">The assembly to search</param>
    /// <param name="predicate">The predicate query to match against</param>
    /// <returns>The collection of types within the assembly that match the predicate</returns>
    public static ICollection<Type> GetMatchingTypesInAssembly(this Assembly assembly, Predicate<Type> predicate)
    {
        ICollection<Type> types = new List<Type>();
        try
        {
            types = assembly.GetTypes().Where(i => i != null && predicate(i) && i.Assembly == assembly).ToList();
        }
        catch (ReflectionTypeLoadException ex)
        {
            foreach (Type theType in ex.Types)
            {
                try
                {
                    if (theType != null && predicate(theType) && theType.Assembly == assembly)
                        types.Add(theType);
                }
                // This exception list is not exhaustive, modify to suit any reasons
                // you find for failure to parse a single assembly
                catch (BadImageFormatException)
                {
                    // Type not in this assembly - reference to elsewhere ignored
                }
            }
        }
        return types;
    }
sweetfa
quelle
4

Haben Sie über Assembly.ReflectionOnlyLoad nachgedacht ? In Anbetracht dessen, was Sie versuchen, könnte es ausreichen.

Seb
quelle
2
Ja, das hatte ich mir überlegt. Aber ich habe es nicht benutzt, weil ich sonst alle Abhängigkeiten manuell laden müsste. Außerdem wäre der Code mit ReflectionOnlyLoad nicht ausführbar (siehe Abschnitt "Anmerkungen" auf der von Ihnen verlinkten Seite).
M4N
3

In meinem Fall wurde das gleiche Problem durch das Vorhandensein unerwünschter Assemblys im Anwendungsordner verursacht. Versuchen Sie, den Ordner Bin zu löschen und die Anwendung neu zu erstellen.

Sergey
quelle