Pergunta

Pergunta baseado em MSDN exemplo .

Vamos dizer que temos algumas classes C # com HelpAttribute em aplicação desktop standalone. É possível enumerar todas as classes com tal atributo? Faz sentido de reconhecer as classes dessa maneira? atributo personalizado seria usado para listar possíveis opções de menu, selecionando o item trará à instância tela de tal classe. Número de classes / itens vai crescer lentamente, mas desta forma podemos evitar enumerando todos eles em outro lugar, eu acho.

Foi útil?

Solução

Sim, absolutamente. Usando Reflexão:

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

Outras dicas

Bem, você teria que enumerar todas as classes em todos os conjuntos que são carregados no domínio de aplicativo atual. Para fazer isso, você chamaria o href="http://msdn.microsoft.com/en-us/library/system.appdomain.getassemblies.aspx" rel="noreferrer"> método GetAssemblies em AppDomain instância para o domínio de aplicativo atual.

A partir daí, você chamaria GetExportedTypes (se só deseja tipos públicos) ou GetTypes em cada Assembly para obter os tipos que estão contidos no conjunto .

Em seguida, você chamaria a GetCustomAttributes método em cada Type exemplo, passando o tipo de atributo que você deseja encontrar .

Você pode usar LINQ para simplificar isso para você:

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

A consulta acima irá obter cada tipo com o seu atributo aplicado a ele, juntamente com a instância do atributo (s) atribuído a ele.

Note que se você tiver um grande número de conjuntos carregados no seu domínio de aplicação, que a operação poderia ser caro. Você pode usar Parallel LINQ para reduzir o tempo da operação, como assim :

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

filtrá-la em um Assembly específico é simples:

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

E se a montagem tem um grande número de tipos de em-lo, então você pode usar LINQ paralelo novamente:

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

Outras respostas referência GetCustomAttributes . Adicionando este como um exemplo do uso IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;

Como já foi dito, a reflexão é o caminho a percorrer. Se você estiver indo para chamar isso com freqüência, eu sugiro cache os resultados, como a reflexão, especialmente enumerando através de cada classe, pode ser bastante lento.

Este é um trecho do meu código que atravessa todos os tipos em todos os conjuntos carregada:

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

Esta é uma melhoria de desempenho no topo da solução aceita. Iteração embora todas as classes podem ser lento, porque há tantos. Às vezes você pode filtrar uma montagem inteira sem olhar para qualquer um dos seus tipos.

Por exemplo, se você está procurando um atributo que você declarou-se, você não espera qualquer uma das DLLs do sistema para conter quaisquer tipos com esse atributo. A propriedade Assembly.GlobalAssemblyCache é uma maneira rápida para verificar se há DLLs do sistema. Quando eu tentei isso em um programa real que eu descobri que podia pular 30,101 tipos e eu só tenho que verificar 1.983 tipos.

Outra forma de filtro é usar Assembly.ReferencedAssemblies. Presumivelmente, se você quiser classes com um atributo específico, e esse atributo é definido em um específico montagem, então você só se preocupam com que a montagem e outros conjuntos que fazer referência a ela. Em meus testes isso ajudou um pouco mais do que verificar a propriedade GlobalAssemblyCache.

Eu combinei ambos e tenho-o ainda mais rápido. O código abaixo inclui ambos os filtros.

        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)

No caso do .NET portátil limitações , o seguinte código deve funcionar:

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

ou para um grande número de montagens usando yield return circuito de estado baseado:

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

Podemos melhorar a resposta de Andrew e converter a coisa toda em uma consulta LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top