Bagaimana cara menghitung semua kelas dengan atribut kelas khusus?

151

Pertanyaan berdasarkan contoh MSDN .

Katakanlah kita memiliki beberapa kelas C # dengan HelpAttribute di aplikasi desktop mandiri. Apakah mungkin untuk menghitung semua kelas dengan atribut seperti itu? Apakah masuk akal untuk mengenali kelas dengan cara ini? Atribut khusus akan digunakan untuk membuat daftar opsi menu yang mungkin, memilih item akan membawa ke instance layar kelas tersebut. Jumlah kelas / item akan tumbuh lambat, tetapi dengan cara ini kita dapat menghindari penghitungan semuanya di tempat lain, saya pikir.

Tomash
sumber

Jawaban:

205

Ya, tentu saja. Menggunakan Refleksi:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}
Andrew Arnott
sumber
7
Setuju, tetapi dalam hal ini kita dapat melakukannya secara deklaratif sesuai solusi casperOne. Itu bagus untuk dapat menggunakan hasil, itu bahkan lebih baik tidak harus :)
Jon Skeet
9
Saya suka LINQ. Senang, sebenarnya. Tetapi dibutuhkan ketergantungan pada. NET 3.5, yang menghasilkan pengembalian tidak. Selain itu, LINQ akhirnya terurai menjadi hal yang pada dasarnya sama dengan imbal hasil. Jadi, apa yang telah Anda dapatkan? Sintaks C # tertentu, yaitu preferensi.
Andrew Arnott
1
@AndrewArnott Baris kode yang paling pendek dan paling pendek tidak relevan dengan kinerja, mereka hanya kontributor yang memungkinkan untuk keterbacaan dan pemeliharaan. Saya menantang pernyataan bahwa mereka mengalokasikan objek paling sedikit dan kinerja akan lebih cepat (terutama tanpa bukti empiris); Anda pada dasarnya telah menulis Selectmetode ekstensi, dan kompiler akan menghasilkan mesin negara seperti halnya jika Anda menelepon Selectkarena penggunaan Anda yield return. Akhirnya, setiap perolehan kinerja yang mungkin diperoleh dalam sebagian besar kasus adalah optimasi mikro.
casperOne
1
Benar sekali, @casperOne. Perbedaan yang sangat kecil, terutama dibandingkan dengan bobot refleksi itu sendiri. Mungkin tidak akan pernah muncul dalam jejak perf.
Andrew Arnott
1
Tentu saja Resharper mengatakan "bahwa foreach loop dapat dikonversi menjadi ekspresi LINQ" yang terlihat seperti ini: assembly.GetTypes (). Di mana (type => type.GetCustomAttributes (typeof (HelpAttribute), true) .Length> 0);
David Barrows
107

Nah, Anda harus menghitung melalui semua kelas di semua majelis yang dimuat ke domain aplikasi saat ini. Untuk melakukan itu, Anda akan memanggil GetAssembliesmetode pada AppDomaincontoh untuk domain aplikasi saat ini.

Dari sana, Anda akan menelepon GetExportedTypes(jika Anda hanya ingin tipe publik) atau GetTypesmasing Assembly- masing untuk mendapatkan tipe yang terkandung dalam majelis.

Kemudian, Anda akan memanggil GetCustomAttributesmetode ekstensi pada setiap Typeinstance, melewati tipe atribut yang ingin Anda temukan.

Anda dapat menggunakan LINQ untuk menyederhanakan ini untuk Anda:

var typesWithMyAttribute =
    from a in AppDomain.CurrentDomain.GetAssemblies()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Kueri di atas akan memberi Anda setiap jenis dengan atribut yang diterapkan padanya, bersama dengan instance atribut yang ditugaskan padanya.

Perhatikan bahwa jika Anda memiliki banyak rakitan yang dimuat ke domain aplikasi Anda, operasi itu bisa mahal. Anda dapat menggunakan LINQ Paralel untuk mengurangi waktu operasi, seperti:

var typesWithMyAttribute =
    // Note the AsParallel here, this will parallelize everything after.
    from a in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Memfilternya pada spesifik Assemblyadalah sederhana:

Assembly assembly = ...;

var typesWithMyAttribute =
    from t in assembly.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Dan jika perakitan memiliki sejumlah besar jenis di dalamnya, maka Anda dapat menggunakan LINQ paralel:

Assembly assembly = ...;

var typesWithMyAttribute =
    // Partition on the type list initially.
    from t in assembly.GetTypes().AsParallel()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };
casperOne
sumber
1
Menghitung semua tipe dalam semua rakitan yang dimuat hanya akan sangat lambat dan tidak banyak menguntungkan Anda. Ini juga berpotensi risiko keamanan. Anda mungkin dapat memperkirakan rakitan mana yang akan berisi jenis yang Anda minati. Cukup sebutkan jenisnya.
Andrew Arnott
@Andrew Arnott: Benar, tapi ini yang diminta. Cukup mudah untuk memangkas kueri untuk perakitan tertentu. Ini juga memiliki manfaat tambahan untuk memberi Anda pemetaan antara tipe dan atribut.
casperOne
1
Anda dapat menggunakan kode yang sama hanya pada perakitan saat ini dengan System.Reflection.Assembly.GetExecutingAssembly ()
Chris Moschini
@ChrisMoschini Ya, Anda bisa, tetapi Anda mungkin tidak selalu ingin memindai rakitan saat ini. Lebih baik membiarkannya terbuka.
casperOne
Saya sudah melakukan ini berkali-kali, dan tidak ada banyak cara untuk membuatnya efisien. Anda dapat melewati majelis microsoft (mereka ditandatangani dengan kunci yang sama, sehingga mereka cukup mudah untuk menghindari menggunakan AssemblyName. Anda dapat menyimpan hasil cache dalam statis, yang unik untuk AppDomain di mana majelis dimuat (harus cache penuh nama-nama majelis yang Anda periksa untuk berjaga-jaga seandainya ada orang lain yang memuatnya sementara ini.) Saya menemukan diri saya di sini karena saya sedang menyelidiki contoh-contoh caching yang dimuat dari tipe atribut dalam atribut tersebut. Tidak yakin dengan pola itu, tidak yakin kapan mereka dipakai, dll.
34

Referensi jawaban lain GetCustomAttributes . Menambahkan yang ini sebagai contoh menggunakan IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;
Jay Walker
sumber
3
Saya percaya itu solusi tepat yang menggunakan metode kerangka kerja yang dimaksudkan.
Alexey Omelchenko
11

Seperti yang telah dinyatakan, refleksi adalah jalan yang harus ditempuh. Jika Anda akan sering menyebutnya, saya sangat menyarankan untuk menyimpan hasilnya, karena refleksi, terutama menghitung melalui setiap kelas, bisa sangat lambat.

Ini adalah potongan kode saya yang menjalankan semua jenis di semua rakitan yang dimuat:

// this is making the assumption that all assemblies we need are already loaded.
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 
{
    foreach (Type type in assembly.GetTypes())
    {
        var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
        if (attribs != null && attribs.Length > 0)
        {
            // add to a cache.
        }
    }
}
CodingWithSpike
sumber
9

Ini adalah peningkatan kinerja di atas solusi yang diterima. Iterasi meskipun semua kelas bisa lambat karena ada begitu banyak. Kadang-kadang Anda dapat menyaring seluruh unit tanpa melihat jenisnya.

Misalnya, jika Anda mencari atribut yang Anda nyatakan sendiri, Anda tidak mengharapkan DLL sistem mengandung tipe apa pun dengan atribut itu. Properti Assembly.GlobalAssemblyCache adalah cara cepat untuk memeriksa DLL sistem. Ketika saya mencoba ini pada program nyata saya menemukan saya bisa melewati 30.101 jenis dan saya hanya perlu memeriksa 1.983 jenis.

Cara lain untuk memfilter adalah dengan menggunakan Assembly.ReferencedAssemblies. Agaknya jika Anda ingin kelas dengan atribut tertentu, dan atribut itu didefinisikan dalam rakitan tertentu, maka Anda hanya peduli tentang rakitan itu dan rakitan lain yang merujuknya. Dalam pengujian saya ini membantu sedikit lebih banyak daripada memeriksa properti GlobalAssemblyCache.

Saya menggabungkan keduanya dan membuatnya lebih cepat. Kode di bawah ini mencakup kedua filter.

        string definedIn = typeof(XmlDecoderAttribute).Assembly.GetName().Name;
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            // Note that we have to call GetName().Name.  Just GetName() will not work.  The following
            // if statement never ran when I tried to compare the results of GetName().
            if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
                foreach (Type type in assembly.GetTypes())
                    if (type.GetCustomAttributes(typeof(XmlDecoderAttribute), true).Length > 0)
Ide Dagang Philip
sumber
4

Dalam hal batasan .NET Portable , kode berikut harus berfungsi:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        var typesAttributed =
            from assembly in assemblies
            from type in assembly.DefinedTypes
            where type.IsDefined(attributeType, false)
            select type;
        return typesAttributed;
    }

atau untuk sejumlah besar majelis yang menggunakan loop-state based yield return:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        foreach (var assembly in assemblies)
        {
            foreach (var typeInfo in assembly.DefinedTypes)
            {
                if (typeInfo.IsDefined(attributeType, false))
                {
                    yield return typeInfo;
                }
            }
        }
    }
Lorenz Lo Sauer
sumber
0

Kami dapat memperbaiki jawaban Andrew dan mengonversi semuanya menjadi satu permintaan LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Tachyon
sumber