Özel sınıf özelliğine sahip tüm sınıfları nasıl numaralandırır?


151

MSDN örneğine dayalı soru .

Bağımsız masaüstü uygulamasında HelpAttribute ile bazı C # sınıflarımız olduğunu varsayalım. Bu nitelikteki tüm sınıfları numaralandırmak mümkün müdür? Sınıfları bu şekilde tanımak mantıklı mı? Özel öznitelik olası menü seçeneklerini listelemek için kullanılır, öğe seçildiğinde bu sınıfın ekran örneğine getirilir. Sınıfların / öğelerin sayısı yavaş büyüyecek, ancak bu şekilde hepsini başka bir yerde numaralandırmaktan kaçınabiliriz.

Yanıtlar:


205

Evet kesinlikle. Yansıma kullanma:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

7
Kabul etti, ancak bu durumda bunu casperOne'un çözümüne göre deklaratif olarak yapabiliriz. Verimi kullanabilmek güzel, gerekmemek daha da güzel :)
Jon Skeet

9
LINQ'yu seviyorum. Aslında onu seviyorum. Ancak, getiri getirmez .NET 3.5 bağımlılık gerektirir. Ayrıca, LINQ nihayetinde verim getirisi ile aynı şeylere dönüşür. Peki ne kazandın? Belirli bir C # sözdizimi, bu bir tercihtir.
Andrew Arnott

1
@AndrewArnott En az ve en kısa kod satırı performansla ilgisizdir, yalnızca okunabilirliğe ve sürdürülebilirliğe katkıda bulunurlar. En az sayıda nesneyi ayırdıklarına ve performansın daha hızlı olacağına (özellikle ampirik kanıt olmadan); temel olarak Selectuzantı yöntemini yazdınız ve derleyici, Selectkullanımınız nedeniyle aradığınız gibi bir durum makinesi üretecektir yield return. Son olarak, herhangi bir performans kazançları olabilir vakaların çoğunda elde edilecek mikro optimizasyonlar olmak.
casperOne

1
Oldukça doğru, @casperOne. Özellikle yansımanın ağırlığı ile karşılaştırıldığında çok küçük bir fark. Muhtemelen asla mükemmel bir iz bırakmazdı.
Andrew Arnott

1
Tabii ki Resharper, "bu foreach döngüsü bir LINQ ifadesine dönüştürülebilir" diyor ki: assembly.GetTypes (). Burada (type => type.GetCustomAttributes (typeof (HelpAttribute), true) .Length> 0);
David Barrows

107

Geçerli uygulama etki alanına yüklenen tüm derlemelerdeki tüm sınıfları numaralandırmanız gerekir. Bunu yapmak için, çağırır GetAssembliesyöntemi üzerinde AppDomainmevcut uygulama etki alanı için örneğin.

Oradan , derlemede bulunan türleri almak için GetExportedTypes(yalnızca genel türler istiyorsanız) veya GetTypesher Assemblybirini ararsınız.

Ardından, bulmak istediğiniz özniteliğin türünü ileterek her örnekte GetCustomAttributesuzantı yöntemini çağırırsınız Type.

Bunu sizin için basitleştirmek için LINQ kullanabilirsiniz:

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

Yukarıdaki sorgu, kendinize atanan özniteliklerin yanı sıra, özniteliğinizin kendisine uygulandığı her türü alır.

Uygulama alanınıza çok sayıda derleme yüklüyse, bu işlemin pahalı olabileceğini unutmayın. Operasyon süresini azaltmak için Paralel LINQ kullanabilirsiniz , örneğin:

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

Belirli bir öğeye filtre Assemblyuygulamak basittir:

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

Derlemede çok sayıda tür varsa, Paralel LINQ'yu tekrar kullanabilirsiniz:

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

1
İçinde her türlü numaralandırma tüm yüklü meclisleri sadece çok yavaş olabilir ve size çok kazanç olmaz. Ayrıca potansiyel olarak bir güvenlik riskidir. Muhtemelen hangi montajların ilgilendiğiniz türleri içerdiğini tahmin edebilirsiniz. Sadece bu türleri numaralandırın.
Andrew Arnott

@Andrew Arnott: Doğru, ama istenen şey bu. Belirli bir derleme için sorguyu budamak yeterince kolaydır. Bu aynı zamanda size tür ve öznitelik arasındaki eşlemeyi vermenin ek bir yararı da vardır.
casperOne

1
System.Reflection.Assembly.GetExecutingAssembly ()
Chris Moschini

@ChrisMoschini Evet, yapabilirsiniz, ancak geçerli montajı her zaman taramak istemeyebilirsiniz. Açık bırakmak daha iyi.
Haz12

Bunu birçok kez yaptım ve verimli hale getirmenin pek bir yolu yok. Microsoft derlemelerini atlayabilirsiniz (aynı anahtarla imzalanırlar, bu nedenle AssemblyName'i kullanmaktan kaçınmak oldukça kolaydır. Sonuçların, derlemelerin yüklü olduğu AppDomain'e özgü bir statik içinde önbelleğe alınabilmelerini sağlar (tam önbelleğe almaları gerekir) diğerlerinin yüklü olması durumunda kontrol ettiğiniz derlemelerin isimleri). Kendimi burada buldum, öznitelik içinde bir öznitelik türünün yüklü örneklerinin önbelleğe alınmasını araştırıyorum. Bu modelden emin değilim, ne zaman somutlaştırıldıklarından emin değilim.


11

Daha önce de belirtildiği gibi, yansıma yoludur. Bunu sık sık arayacaksanız, sonuçların önbelleğe alınmasını öneririm, özellikle yansıma, özellikle her sınıfta numaralandırma, oldukça yavaş olabilir.

Bu, yüklü tüm montajlarda tüm türleri üzerinden çalışan benim kod pasajı:

// 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.
        }
    }
}

9

Bu, kabul edilen çözümün üstünde bir performans geliştirmesidir. Tüm sınıflar yavaş olsa da yineleniyor çünkü çok fazla var. Bazen, herhangi bir türüne bakmadan tüm montajı filtreleyebilirsiniz.

Örneğin, kendiniz bildirdiğiniz bir öznitelik arıyorsanız, sistem DLL'lerinin hiçbirinin bu özniteliğe sahip herhangi bir tür içermesini beklemezsiniz. Assembly.GlobalAssemblyCache özelliği sistem DLL'lerini kontrol etmenin hızlı bir yoludur. Bunu gerçek bir programda denediğimde 30.101 tipini atlayabildiğimi ve sadece 1.983 tipini kontrol etmem gerektiğini buldum.

Filtrelemenin başka bir yolu, Assembly.ReferencedAssemblies kullanmaktır. Muhtemelen belirli bir niteliğe sahip sınıflar istiyorsanız ve bu özellik belirli bir derlemede tanımlanmışsa, yalnızca bu derlemeyi ve ona başvuran diğer derlemeleri önemsersiniz. Testlerimde bu, GlobalAssemblyCache özelliğini kontrol etmekten biraz daha fazla yardımcı oldu.

Her ikisini de birleştirdim ve daha da hızlı hale getirdim. Aşağıdaki kod her iki filtreyi de içerir.

        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)

4

Portable .NET sınırlamaları durumunda , aşağıdaki kodun çalışması gerekir:

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

veya döngü durumu tabanlı çok sayıda montaj için 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;
                }
            }
        }
    }

0

Andrew'un cevabını geliştirebilir ve her şeyi bir LINQ sorgusuna dönüştürebiliriz.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.