Bagaimana mencegah ReflectionTypeLoadException saat memanggil Assembly.GetTypes ()

97

Saya mencoba memindai rakitan untuk jenis yang menerapkan antarmuka tertentu menggunakan kode yang mirip dengan ini:

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

Masalah saya adalah, bahwa saya mendapatkan ReflectionTypeLoadExceptionketika memanggil asm.GetTypes()dalam beberapa kasus, misalnya jika assembly berisi jenis referensi assembly yang saat ini tidak tersedia.

Dalam kasus saya, saya tidak tertarik dengan jenis yang menyebabkan masalah. Tipe yang saya cari tidak memerlukan rakitan yang tidak tersedia.

Pertanyaannya adalah: apakah mungkin untuk melewati / mengabaikan tipe yang menyebabkan pengecualian tetapi masih memproses tipe lain yang terdapat dalam assembly?

M4N
sumber
1
Ini mungkin lebih merupakan penulisan ulang daripada apa yang Anda cari, tetapi MEF memberi Anda fungsionalitas yang serupa. Cukup tandai setiap kelas Anda dengan tag [Ekspor] yang menentukan antarmuka yang diimplementasikannya. Kemudian Anda hanya dapat mengimpor antarmuka yang Anda minati saat itu.
Dirk Dastardly
@Drew, Terima kasih atas komentar Anda. Saya sedang berpikir untuk menggunakan MEF, tetapi ingin melihat apakah ada solusi lain yang lebih murah.
M4N
Memberi pabrik kelas plugin nama terkenal sehingga Anda bisa langsung menggunakan Activator.CreateInstance () adalah solusi sederhana. Namun demikian, jika Anda mendapatkan pengecualian ini sekarang karena masalah resolusi perakitan maka Anda mungkin akan mendapatkannya nanti juga.
Hans Passant
1
@ Hans: Saya tidak yakin saya sepenuhnya mengerti. Rakitan yang saya pindai mungkin berisi beberapa jenis yang mengimplementasikan antarmuka yang diberikan, jadi tidak ada satu jenis yang terkenal. (dan juga: Saya memindai lebih dari satu perakitan, tidak hanya satu)
M4N
2
Saya memiliki kode yang hampir sama, dan masalah yang sama. Dan perakitan yang saya jelajahi diberikan AppDomain.CurrentDomain.GetAssemblies(), ini berfungsi pada mesin saya tetapi tidak pada mesin lain. Mengapa sih beberapa rakitan dari executable saya tidak dapat dibaca / dimuat pula ??
v.oddou

Jawaban:

130

Salah satu cara yang cukup buruk adalah:

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

Ini pasti menjengkelkan harus melakukan ini. Anda dapat menggunakan metode ekstensi untuk membuatnya lebih baik di kode "klien":

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

Anda mungkin ingin memindahkan returnpernyataan keluar dari blok tangkap - saya tidak terlalu tertarik untuk berada di sana sendiri, tetapi mungkin itu adalah kode terpendek ...

Jon Skeet
sumber
2
Terima kasih, sepertinya itu adalah solusi (dan saya setuju, ini sepertinya bukan solusi yang bersih).
M4N
4
Solusi ini masih mengalami masalah saat Anda mencoba menggunakan daftar jenis yang diekspos dalam pengecualian. Apa pun alasan pengecualian pemuatan jenis, FileNotFound, BadImage, dll., Akan tetap menggunakan setiap akses ke jenis yang menjadi masalah.
sweetfa
@sweetfa: Ya, itu sangat terbatas - tetapi jika OP hanya perlu mencari nama, misalnya, tidak apa-apa.
Jon Skeet
1
Lucu, posting ini dikutip di sini, cukup menarik: haacked.com/archive/2012/07/23/…
anhoppe
@sweetfa Inilah yang saya lakukan untuk menghindari masalah pengecualian FileNotFound pada jenis yang dikembalikan: From t As Type In e.Types Where (t IsNot Nothing) AndAlso (t.TypeInitializer IsNot Nothing)Tampaknya berfungsi dengan baik.
ElektroStudios
22

Meskipun tampaknya tidak ada yang dapat dilakukan tanpa menerima ReflectionTypeLoadException di beberapa titik, jawaban di atas terbatas karena setiap upaya untuk memanfaatkan jenis yang disediakan dari pengecualian akan tetap memberikan masalah dengan masalah asli yang menyebabkan jenis tersebut gagal dimuat.

Untuk mengatasi ini, kode berikut membatasi jenis yang terletak di dalam rakitan dan memungkinkan predikat untuk lebih membatasi daftar jenis.

    /// <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
sumber
4

Sudahkah Anda mempertimbangkan Assembly.ReflectionOnlyLoad ? Mempertimbangkan apa yang Anda coba lakukan, itu mungkin cukup.

Seb
sumber
2
Ya, saya telah mempertimbangkan itu. Tetapi saya tidak menggunakannya karena jika tidak saya harus memuat dependensi apa pun secara manual. Juga kode tidak akan bisa dieksekusi dengan ReflectionOnlyLoad (lihat bagian Keterangan di halaman yang Anda tautkan).
M4N
3

Dalam kasus saya, masalah yang sama disebabkan oleh adanya rakitan yang tidak diinginkan di folder aplikasi. Cobalah untuk menghapus folder Bin dan membangun kembali aplikasi.

Sergey
sumber