Como evitar ReflectionTypeLoadException ao chamar Assembly.GetTypes ()

97

Estou tentando escanear um assembly em busca de tipos que implementam uma interface específica usando um código semelhante 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;
}

Meu problema é que recebo um ReflectionTypeLoadExceptionao chamar asm.GetTypes()em alguns casos, por exemplo, se o assembly contém tipos que fazem referência a um assembly que não está disponível no momento.

No meu caso, não estou interessado nos tipos que causam o problema. Os tipos que procuro não precisam de assemblies não disponíveis.

A questão é: é possível pular / ignorar de alguma forma os tipos que causam a exceção, mas ainda assim processar os outros tipos contidos na montagem?

M4N
fonte
1
Pode ser muito mais uma reescrita do que o que você está procurando, mas o MEF oferece uma funcionalidade semelhante. Apenas marque cada uma de suas classes com uma tag [Export] que especifica a interface que ela implementa. Em seguida, você pode importar apenas as interfaces de seu interesse no momento.
Dirk Dastardly,
@Drew, Obrigado pelo seu comentário. Estava pensando em usar o MEF, mas queria ver se existe outra solução mais barata.
M4N
Dar à fábrica de classes de plugins um nome bem conhecido para que você possa usar apenas Activator.CreateInstance () diretamente é uma solução simples. No entanto, se você obtiver essa exceção agora devido a um problema de resolução de assembly, provavelmente também a obterá mais tarde.
Hans Passant,
1
@Hans: Não tenho certeza se entendi completamente. A montagem que estou digitalizando pode conter qualquer número de tipos que implementam a interface fornecida, portanto, não há um tipo bem conhecido. (e também: estou digitalizando mais de uma montagem, não apenas uma)
M4N
2
Tenho quase o mesmo código e o mesmo problema. E a montagem que eu exploro é fornecida por AppDomain.CurrentDomain.GetAssemblies(), isso funciona na minha máquina, mas não em outras máquinas. Por que diabos alguns assemblies do meu executável não podem ser lidos / carregados de qualquer maneira?
v.oddou 01 de

Respostas:

130

Uma maneira bastante desagradável seria:

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

É definitivamente irritante ter que fazer isso. Você pode usar um método de extensão para torná-lo mais agradável no código do "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);
    }
}

Você pode muito bem querer mover a returninstrução para fora do bloco catch - eu não estou muito interessado em que ela esteja lá, mas provavelmente é o código mais curto ...

Jon Skeet
fonte
2
Obrigado, parece ser uma solução (e concordo, não parece uma solução limpa).
M4N
4
Esta solução ainda apresenta problemas quando você tenta usar a lista de tipos expostos na exceção. Seja qual for o motivo da exceção de carregamento de tipo, FileNotFound, BadImage, etc, ainda irá lançar em todos os acessos aos tipos que estão em questão.
sweetfa
@sweetfa: Sim, é muito limitado - mas se o OP precisar apenas encontrar os nomes, por exemplo, tudo bem.
Jon Skeet,
1
Engraçado, esse post é citado aqui, bem interessante: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa Isso é o que eu faço para evitar o problema da exceção FileNotFound nos tipos retornados: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)Parece funcionar muito bem.
ElektroStudios
22

Embora pareça que nada pode ser feito sem receber a ReflectionTypeLoadException em algum ponto, as respostas acima são limitadas, pois qualquer tentativa de utilizar os tipos fornecidos na exceção ainda apresentará problemas com o problema original que causou a falha no carregamento do tipo.

Para superar isso, o código a seguir limita os tipos àqueles localizados dentro do assembly e permite que um predicado restrinja ainda mais a 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;
    }
Sweetfa
fonte
4

Você já considerou Assembly.ReflectionOnlyLoad ? Considerando o que você está tentando fazer, pode ser o suficiente.

Seb
fonte
2
Sim, eu tinha considerado isso. Mas eu não usei porque senão teria que carregar manualmente as dependências. Além disso, o código não seria executável com ReflectionOnlyLoad (consulte a seção Comentários na página vinculada).
M4N
3

No meu caso, o mesmo problema foi causado pela presença de assemblies indesejados na pasta do aplicativo. Tente limpar a pasta Bin e reconstruir o aplicativo.

Sergey
fonte