Jak zapobiec ReflectionTypeLoadException podczas wywoływania Assembly.GetTypes ()

97

Próbuję przeskanować zestaw pod kątem typów implementujących określony interfejs przy użyciu kodu podobnego do tego:

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

Mój problem polega na tym, że ReflectionTypeLoadExceptionpodczas wywoływania asm.GetTypes()w niektórych przypadkach otrzymuję a , np. Jeśli zestaw zawiera typy odwołujące się do zestawu, który jest obecnie niedostępny.

W moim przypadku nie interesują mnie typy, które powodują problem. Typy, których szukam, nie wymagają niedostępnych zestawów.

Pytanie brzmi: czy można w jakiś sposób pominąć / zignorować typy, które powodują wyjątek, ale nadal przetwarzać inne typy zawarte w zestawie?

M4N
źródło
1
Może to być znacznie więcej przepisywania niż tego, czego szukasz, ale MEF zapewnia podobną funkcjonalność. Po prostu oznacz każdą z klas znacznikiem [Export], który określa interfejs, który implementuje. Następnie możesz zaimportować tylko te interfejsy, które w danym momencie Cię interesują.
Dirk Dastardly,
@Drew, dziękuję za komentarz. Myślałem o użyciu MEF, ale chciałem sprawdzić, czy jest inne, tańsze rozwiązanie.
M4N,
Nadanie fabryce klas wtyczek dobrze znanej nazwy, aby można było po prostu użyć bezpośrednio Activator.CreateInstance () jest prostym obejściem. Niemniej jednak, jeśli otrzymasz ten wyjątek teraz z powodu problemu z rozwiązywaniem asemblera, prawdopodobnie otrzymasz go również później.
Hans Passant,
1
@Hans: Nie jestem pewien, czy całkowicie rozumiem. Zespół, który skanuję, może zawierać dowolną liczbę typów implementujących dany interfejs, więc nie ma jednego dobrze znanego typu. (a także:
skanuję
2
Mam prawie ten sam kod i ten sam problem. Zespół, który eksploruję, składa się z AppDomain.CurrentDomain.GetAssemblies()tego, że działa na moim komputerze, ale nie na innych komputerach. Dlaczego do cholery niektóre zestawy z mojego pliku wykonywalnego i tak nie mogą być odczytywane / ładowane?
v.oddou

Odpowiedzi:

130

Jednym dość nieprzyjemnym sposobem byłoby:

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

To zdecydowanie denerwujące, że trzeba to robić. Możesz użyć metody rozszerzenia, aby była ładniejsza w kodzie „klienta”:

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

Możesz chcieć returnusunąć instrukcję z bloku catch - ja sam nie bardzo chcę, żeby tam było, ale prawdopodobnie jest to najkrótszy kod ...

Jon Skeet
źródło
2
Dzięki, to wydaje się być rozwiązaniem (i zgadzam się, nie wydaje się być czystym rozwiązaniem).
M4N,
4
To rozwiązanie nadal powoduje problemy podczas próby użycia listy typów ujawnionych w wyjątku. Bez względu na przyczynę wyjątku ładowania typu, FileNotFound, BadImage itp. Nadal będą rzucać przy każdym dostępie do typów, których dotyczy problem.
sweetfa
@sweetfa: Tak, jest bardzo ograniczone - ale jeśli OP musi tylko znaleźć nazwy, na przykład, powinno być w porządku.
Jon Skeet,
1
Zabawne, ten post jest tutaj cytowany, całkiem interesujący: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa Oto, co robię, aby uniknąć problemu z wyjątkiem FileNotFound w zwracanych typach: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)Wydaje się, że działa świetnie.
ElektroStudios
22

Chociaż wydaje się, że nic nie można zrobić bez otrzymania w pewnym momencie wyjątku ReflectionTypeLoadException, powyższe odpowiedzi są ograniczone, ponieważ każda próba wykorzystania typów dostarczonych z wyjątku nadal będzie powodować problem z pierwotnym problemem, który spowodował, że typ nie załadował się.

Aby rozwiązać ten problem, poniższy kod ogranicza typy do tych znajdujących się w zestawie i umożliwia predykatowi dalsze ograniczenie listy typów.

    /// <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
źródło
4

Czy rozważałeś Assembly.ReflectionOnlyLoad ? Biorąc pod uwagę to, co próbujesz zrobić, może to wystarczyć.

Seb
źródło
2
Tak, rozważałem to. Ale nie użyłem go, ponieważ w przeciwnym razie musiałbym ręcznie załadować wszelkie zależności. Kod nie byłby również wykonywalny z ReflectionOnlyLoad (zobacz sekcję Uwagi na stronie, do której jesteś podłączony).
M4N
3

W moim przypadku ten sam problem był spowodowany obecnością niechcianych zestawów w folderze aplikacji. Spróbuj wyczyścić folder Bin i odbuduj aplikację.

Siergiej
źródło