Comment empêcher ReflectionTypeLoadException lors de l'appel de Assembly.GetTypes ()

97

J'essaie de scanner un assembly pour les types implémentant une interface spécifique en utilisant un code similaire à celui-ci:

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

Mon problème est que j'obtiens un ReflectionTypeLoadExceptionlors de l'appel asm.GetTypes()dans certains cas, par exemple si l'assembly contient des types référençant un assembly qui n'est actuellement pas disponible.

Dans mon cas, je ne suis pas intéressé par les types qui causent le problème. Les types que je recherche n'ont pas besoin des assemblys non disponibles.

La question est la suivante: est-il possible d'ignorer / ignorer d'une manière ou d'une autre les types qui provoquent l'exception tout en traitant les autres types contenus dans l'assembly?

M4N
la source
1
Cela peut être beaucoup plus une réécriture que ce que vous recherchez, mais MEF vous offre des fonctionnalités similaires. Marquez simplement chacune de vos classes avec une balise [Export] qui spécifie l'interface qu'elle implémente. Ensuite, vous ne pouvez importer que les interfaces qui vous intéressent à ce moment-là.
Dirk Dastardly
@Drew, merci pour votre commentaire. Je pensais utiliser MEF, mais je voulais voir s'il existe une autre solution moins chère.
M4N du
Donner à la fabrique de classes de plugins un nom bien connu afin que vous puissiez simplement utiliser Activator.CreateInstance () directement est une solution de contournement simple. Néanmoins, si vous obtenez cette exception maintenant en raison d'un problème de résolution d'assembly, vous l'obtiendrez probablement plus tard également.
Hans Passant
1
@Hans: Je ne suis pas sûr de bien comprendre. L'assemblage que je scanne peut contenir n'importe quel nombre de types implémentant l'interface donnée, il n'y a donc pas un type bien connu. (et aussi: je scanne plus d'un assemblage, pas un seul)
M4N
2
J'ai presque le même code et le même problème. Et l'assemblage que j'explore est donné par AppDomain.CurrentDomain.GetAssemblies(), cela fonctionne sur ma machine mais pas sur d'autres machines. Pourquoi diable certains assemblys de mon exécutable ne seraient-ils pas lisibles / chargés de toute façon ??
v.oddou

Réponses:

130

Une manière assez désagréable serait:

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

C'est vraiment ennuyeux d'avoir à faire ça. Vous pouvez utiliser une méthode d'extension pour le rendre plus agréable dans le code "client":

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);
    }
}

Vous pouvez bien souhaitez déplacer la returndéclaration sur le bloc catch - Je ne suis pas terriblement vif à ce sujet d' être moi - même, mais il probablement est le plus court code ...

Jon Skeet
la source
2
Merci, cela semble être une solution (et je suis d'accord, cela ne semble pas être une solution propre).
M4N du
4
Cette solution rencontre toujours des problèmes lorsque vous essayez d'utiliser la liste des types exposés dans l'exception. Quelle que soit la raison de l'exception de chargement de type, FileNotFound, BadImage, etc., lancera toujours sur chaque accès aux types en question.
sweetfa
@sweetfa: Oui, c'est très limité - mais si l'OP a juste besoin de trouver les noms, par exemple, ça devrait aller.
Jon Skeet
1
Drôle, ce post est cité ici, assez intéressant: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa C'est ce que je fais pour éviter le problème de l' exception FileNotFound sur les types renvoyés: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)cela semble fonctionner très bien.
ElektroStudios
22

Bien qu'il semble que rien ne puisse être fait sans recevoir l'exception ReflectionTypeLoadException à un moment donné, les réponses ci-dessus sont limitées en ce que toute tentative d'utiliser les types fournis par l'exception posera toujours un problème avec le problème d'origine qui a entraîné l'échec du chargement du type.

Pour surmonter cela, le code suivant limite les types à ceux situés dans l'assembly et permet à un prédicat de restreindre davantage la liste des types.

    /// <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
la source
4

Avez-vous envisagé Assembly.ReflectionOnlyLoad ? Compte tenu de ce que vous essayez de faire, cela pourrait suffire.

Seb
la source
2
Oui, j'avais pensé à cela. Mais je ne l'ai pas utilisé car sinon je devrais charger manuellement toutes les dépendances. De plus, le code ne serait pas exécutable avec ReflectionOnlyLoad (voir la section Remarques sur la page que vous avez liée).
M4N
3

Dans mon cas, le même problème a été causé par la présence d'assemblys indésirables dans le dossier de l'application. Essayez d'effacer le dossier Bin et de reconstruire l'application.

Sergey
la source