Come impedire ReflectionTypeLoadException durante la chiamata a Assembly.GetTypes ()


97

Sto cercando di scansionare un assembly per i tipi che implementano un'interfaccia specifica utilizzando un codice simile a questo:

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

Il mio problema è che ottengo un ReflectionTypeLoadExceptionquando chiamo asm.GetTypes()in alcuni casi, ad esempio se l'assembly contiene tipi che fanno riferimento a un assembly che attualmente non è disponibile.

Nel mio caso, non sono interessato ai tipi che causano il problema. I tipi che cerco non necessitano degli assembly non disponibili.

La domanda è: è possibile in qualche modo saltare / ignorare i tipi che causano l'eccezione ma elaborare comunque gli altri tipi contenuti nell'assembly?


1
Potrebbe essere molto più una riscrittura di quello che stai cercando, ma MEF ti offre funzionalità simili. Contrassegna semplicemente ciascuna delle tue classi con un tag [Export] che specifica l'interfaccia che implementa. Quindi puoi importare solo quelle interfacce che ti interessano in quel momento.
Dirk Dastardly

@Drew, grazie per il tuo commento. Stavo pensando di utilizzare MEF, ma volevo vedere se esiste un'altra soluzione più economica.
M4N

Assegnare al class factory del plugin un nome noto in modo da poter utilizzare direttamente Activator.CreateInstance () è una semplice soluzione alternativa. Tuttavia, se ricevi questa eccezione ora a causa di un problema di risoluzione dell'assembly, probabilmente la otterrai anche in seguito.
Hans Passant

1
@ Hans: non sono sicuro di aver capito completamente. L'assembly che sto analizzando potrebbe contenere un numero qualsiasi di tipi che implementano l'interfaccia data, quindi non esiste un tipo ben noto. (e anche: sto scansionando più di un assieme, non solo uno)
M4N

2
Ho quasi lo stesso codice e lo stesso problema. E l'assieme che esploro è dato da AppDomain.CurrentDomain.GetAssemblies(), funziona sulla mia macchina ma non su altre macchine. Perché diamine alcuni assembly dal mio eseguibile non sarebbero comunque leggibili / caricati ??
v.oddou

Risposte:


130

Un modo abbastanza sgradevole sarebbe:

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

È decisamente fastidioso doverlo fare. Potresti usare un metodo di estensione per renderlo più bello nel codice "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);
    }
}

Potresti desiderare di spostare la returndichiarazione fuori dal blocco di cattura: non mi piace molto che sia lì io stesso, ma probabilmente è il codice più breve ...


2
Grazie, sembra essere una soluzione (e sono d'accordo, non sembra essere una soluzione pulita).
M4N

4
Questa soluzione presenta ancora problemi quando si tenta di utilizzare l'elenco dei tipi esposti nell'eccezione. Qualunque sia la ragione per l'eccezione di caricamento del tipo, FileNotFound, BadImage, ecc. Continuerà a lanciare ogni accesso ai tipi in questione.
sweetfa

@sweetfa: Sì, è molto limitato, ma se l'OP ha solo bisogno di trovare i nomi, per esempio, dovrebbe andare bene.
Jon Skeet

1
Divertente, questo post è citato qui, piuttosto interessante: haacked.com/archive/2012/07/23/…
anhoppe

@sweetfa Questo è quello che faccio per evitare il problema dell'eccezione FileNotFound sui tipi restituiti: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)sembra funzionare alla grande.
ElektroStudios

22

Anche se sembra che nulla possa essere fatto senza ricevere il ReflectionTypeLoadException a un certo punto, le risposte sopra sono limitate in quanto qualsiasi tentativo di utilizzare i tipi forniti dall'eccezione darà comunque problemi con il problema originale che ha causato il mancato caricamento del tipo.

Per ovviare a questo problema, il codice seguente limita i tipi a quelli che si trovano all'interno dell'assembly e consente a un predicato di restringere ulteriormente l'elenco dei tipi.

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

4

Hai considerato Assembly.ReflectionOnlyLoad ? Considerando quello che stai cercando di fare, potrebbe essere sufficiente.


2
Sì, l'avevo considerato. Ma non l'ho usato perché altrimenti avrei dovuto caricare manualmente eventuali dipendenze. Inoltre il codice non sarebbe eseguibile con ReflectionOnlyLoad (vedere la sezione Osservazioni nella pagina collegata).
M4N

3

Nel mio caso, lo stesso problema è stato causato dalla presenza di assembly indesiderati nella cartella dell'applicazione. Prova a cancellare la cartella Bin e ricostruire l'applicazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.