Cómo evitar ReflectionTypeLoadException al llamar a Assembly.GetTypes ()

97

Estoy tratando de escanear un ensamblado en busca de tipos que implementen una interfaz específica usando un código similar a este:

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

Mi problema es que obtengo un ReflectionTypeLoadExceptional llamar asm.GetTypes()en algunos casos, por ejemplo, si el ensamblado contiene tipos que hacen referencia a un ensamblado que actualmente no está disponible.

En mi caso, no me interesan los tipos que causan el problema. Los tipos que estoy buscando no necesitan los ensamblados no disponibles.

La pregunta es: ¿es posible omitir / ignorar de alguna manera los tipos que causan la excepción pero aún así procesar los otros tipos contenidos en el ensamblaje?

M4N
fuente
1
Puede ser mucho más una reescritura de lo que está buscando, pero MEF le brinda una funcionalidad similar. Simplemente marque cada una de sus clases con una etiqueta [Exportar] que especifica la interfaz que implementa. Luego, puede importar solo las interfaces que le interesan en ese momento.
Dirk Dastardly
@Drew, Gracias por tu comentario. Estaba pensando en usar MEF, pero quería ver si hay otra solución más barata.
M4N
Darle a la fábrica de clases de complementos un nombre conocido para que pueda usar Activator.CreateInstance () directamente es una solución simple. Sin embargo, si obtiene esta excepción ahora debido a un problema de resolución de ensamblaje, probablemente también la obtenga más adelante.
Hans Passant
1
@Hans: No estoy seguro de entender completamente. El ensamblado que estoy escaneando puede contener cualquier número de tipos que implementen la interfaz dada, por lo que no hay un tipo conocido. (y también: estoy escaneando más de un ensamblaje, no solo uno)
M4N
2
Tengo casi el mismo código y el mismo problema. Y el ensamblaje que exploro viene dado por AppDomain.CurrentDomain.GetAssemblies(), esto funciona en mi máquina pero no en otras máquinas. ¿Por qué diablos algunos ensamblajes de mi ejecutable no se pueden leer / cargar de todos modos?
v.oddou

Respuestas:

130

Una forma bastante desagradable sería:

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

Sin embargo, definitivamente es molesto tener que hacer esto. Puede utilizar un método de extensión para hacerlo más agradable en el código "cliente":

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

Es posible que desee mover la returndeclaración fuera del bloque de captura; no estoy muy interesado en que esté allí, pero probablemente sea el código más corto ...

Jon Skeet
fuente
2
Gracias, eso parece ser una solución (y estoy de acuerdo, no parece ser una solución limpia).
M4N
4
Esta solución aún presenta problemas cuando intenta utilizar la lista de tipos expuestos en la excepción. Cualquiera sea el motivo de la excepción de carga de tipos, FileNotFound, BadImage, etc., seguirán lanzando todos los accesos a los tipos en cuestión.
sweetfa
@sweetfa: Sí, es muy limitado, pero si el OP solo necesita encontrar los nombres, por ejemplo, debería estar bien.
Jon Skeet
1
Es curioso, esta publicación se cita aquí, bastante interesante: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa Esto es lo que hago para evitar el problema de la excepción FileNotFound en los tipos devueltos: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)parece funcionar muy bien.
ElektroStudios
22

Si bien parece que no se puede hacer nada sin recibir la excepción ReflectionTypeLoadException en algún momento, las respuestas anteriores están limitadas en el sentido de que cualquier intento de utilizar los tipos proporcionados por la excepción seguirá dando problemas con el problema original que causó que el tipo no se cargara.

Para superar esto, el siguiente código limita los tipos a los que se encuentran dentro del ensamblado y permite que un predicado restrinja aún más la lista de tipos.

    /// <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;
    }
dulcefa
fuente
4

¿Ha considerado Assembly.ReflectionOnlyLoad ? Teniendo en cuenta lo que intentas hacer, podría ser suficiente.

Seb
fuente
2
Sí, lo había considerado. Pero no lo usé porque de lo contrario tendría que cargar manualmente las dependencias. Además, el código no sería ejecutable con ReflectionOnlyLoad (consulte la sección Comentarios en la página que vinculó).
M4N
3

En mi caso, el mismo problema fue causado por la presencia de ensamblajes no deseados en la carpeta de la aplicación. Intente borrar la carpeta Bin y reconstruir la aplicación.

Sergey
fuente