How to prevent ReflectionTypeLoadException when calling Assembly.GetTypes()
Asked Answered
K

5

118

I'm trying to scan an assembly for types implementing a specific interface using code similar to this:

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

My problem is, that I get a ReflectionTypeLoadException when calling asm.GetTypes() in some cases, e.g. if the assembly contains types referencing an assembly which is currently not available.

In my case, I'm not interested in the types which cause the problem. The types I'm searching for do not need the non-available assemblies.

The question is: is it possible to somehow skip/ignore the types which cause the exception but still process the other types contained in the assembly?

Killifish answered 25/10, 2011 at 12:26 Comment(5)
It may be a lot more of a rewrite than what you're looking for, but MEF gives you similar functionality. Just mark each of your classes with an [Export] tag that specifies the interface it implements. Then you can import only those interfaces that you are interested in at the time.Callery
@Drew, Thanks for your comment. I was thinking about using MEF, but wanted to see if there is another, cheaper solution.Killifish
Giving the plugin class factory a well-known name so you can just use Activator.CreateInstance() directly is a simple workaround. Nevertheless, if you get this exception now due to an assembly resolution problem then you'll probably get it later as well.Busty
@Hans: I'm not sure I completely understand. The assembly I'm scanning might contain any number of types implementing the given interface, so there is not one well-known type. (and also: I'm scanning more than one assembly, not only one)Killifish
I have almost the same code, and the same problem. And the assembly I explore is given by AppDomain.CurrentDomain.GetAssemblies(), this works on my machine but not on other machines. Why the heck would some assemblies from my executable not be readable/loaded anyway ??Intelsat
G
156

One fairly nasty way would be:

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

It's definitely annoying to have to do this though. You could use an extension method to make it nicer in the "client" code:

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

You may well wish to move the return statement out of the catch block - I'm not terribly keen on it being there myself, but it probably is the shortest code...

Germayne answered 25/10, 2011 at 12:30 Comment(5)
Thanks, that seems to be a solution (and I agree, it doesn't seem to be a clean solution).Killifish
This solution still has issues when you attempt to use the list of types exposed in the exception. Whatever the reason for the type load exception, FileNotFound, BadImage, etc, will still throw on every access to the types that are in issue.Record
@sweetfa: Yes, it's very limited - but if the OP just needs to find the names, for example, it should be okay.Germayne
Funny, this post is cited here, quite interesting: haacked.com/archive/2012/07/23/…Stephanstephana
@Record This is what I do to avoid the problem of FileNotFound exception on the returned types: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing) It seems to work great.Leakey
R
26

Whilst it appears that nothing can be done without receiving the ReflectionTypeLoadException at some point, the answers above are limited in that any attempt to utilise the types provided from the exception will still give issue with the original issue that caused the type to fail to load.

To overcome this the following code limits the types to those located within the assembly and allows a predicate to further restrict the list of 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;
    }
Record answered 16/10, 2012 at 2:55 Comment(0)
R
5

Have you considered Assembly.ReflectionOnlyLoad ? Considering what you're trying to do, it might be enough.

Relucent answered 25/10, 2011 at 14:55 Comment(2)
Yes I had considered that. But I didn't use it because otherwise I would have to manually load any dependencies. Also the code would not be executable with ReflectionOnlyLoad (see Remarks section on the page you linked).Killifish
Assembly.ReflectionOnlyLoad can't be used in .NET Core which the platform is not supported.Tonnage
S
4

The answer from Jon Skeet works fine, but you still get this exception thrown in your face every time. To work around that use the following snippet and turn on "Just My Code" in the debugging settings of Visual Studio.

[DebuggerNonUserCode]
public static IEnumerable<Type> GetSuccesfullyLoadedTypes(Assembly assembly)
{
    try
    {
        return assembly.GetTypes();
    }
    catch (ReflectionTypeLoadException e)
    {
        // If some types can't be loaded, this exception is thrown.
        // The [DebuggerNonUserCode] makes sure these exceptions are not thrown in the developers
        // face when they have "Just My Code" turned on in their debugging settings.
        return e.Types.Where(t => t != null);
    }
}
Silvertongued answered 26/11, 2020 at 13:41 Comment(1)
I have been trying to generate a list of classes in my compiles assemblies that match a specific interface and this, plus another chunk of code to load all assemblies compiled in, has hit it on point. Thanks.Likeminded
F
3

In my case, the same problem was caused by the presence of unwanted assemblies in the application folder. Try to clear the Bin folder and rebuild the application.

Frame answered 3/1, 2016 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.