Pregunta

Pregunta basada en ejemplo de MSDN .

Digamos que tenemos algunas clases de C # con HelpAttribute en una aplicación de escritorio independiente. ¿Es posible enumerar todas las clases con dicho atributo? ¿Tiene sentido reconocer las clases de esta manera? El atributo personalizado se usaría para enumerar las posibles opciones de menú, seleccionando el elemento para mostrar la instancia de dicha clase. El número de clases / elementos crecerá lentamente, pero creo que de esta manera podemos evitar enumerarlos todos en otra parte.

¿Fue útil?

Solución

Sí, absolutamente. Usando la reflexión:

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

Otros consejos

Bueno, tendrías que enumerar a través de todas las clases en todos los ensamblados que se cargan en el dominio de la aplicación actual. Para hacer eso, debe llamar a GetAssemblies método en el AppDomain instancia para el dominio de la aplicación actual.

Desde allí, llamará GetExportedTypes (si solo desea tipos públicos) o GetTypes en cada < código> Ensamblado para obtener los tipos contenidos en el ensamblaje.

Luego, llamaría a GetCustomAtribtributes método en cada Type instancia, pasando el tipo de atributo que desea encontrar.

Puedes usar LINQ para simplificar esto por ti:

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

La consulta anterior le proporcionará cada tipo con su atributo aplicado, junto con la instancia de los atributos asignados.

Tenga en cuenta que si tiene una gran cantidad de ensamblados cargados en el dominio de su aplicación, esa operación podría ser costosa. Puede usar LINQ paralelo para reducir el tiempo de la operación, por lo :

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

Filtrarlo en un Assembly específico es simple:

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

Y si el ensamblaje tiene una gran cantidad de tipos, entonces puedes usar Parallel LINQ nuevamente:

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

Otras respuestas hacen referencia a GetCustomAttributes . Agregando este como ejemplo de uso de IsDefined

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

Como ya se dijo, la reflexión es el camino a seguir. Si va a llamar a esto con frecuencia, le sugiero que almacene en caché los resultados, ya que la reflexión, especialmente la enumeración a través de cada clase, puede ser bastante lenta.

Este es un fragmento de mi código que se ejecuta a través de todos los tipos en todos los ensamblados cargados:

// 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 es una mejora de rendimiento sobre la solución aceptada. Iterar aunque todas las clases puede ser lento porque hay muchas. A veces puede filtrar un ensamblaje completo sin mirar ninguno de sus tipos.

Por ejemplo, si está buscando un atributo que usted mismo declaró, no espera que ninguna de las DLL del sistema contenga ningún tipo con ese atributo. La propiedad Assembly.GlobalAssemblyCache es una forma rápida de verificar las DLL del sistema. Cuando probé esto en un programa real, descubrí que podía saltar 30,101 tipos y solo tengo que verificar 1,983 tipos.

Otra forma de filtrar es usar Assembly.ReferencedAssemblies. Presumiblemente, si desea clases con un atributo específico, y ese atributo se define en un ensamblaje específico, entonces solo se preocupa por ese ensamblaje y otros ensamblajes que lo hacen referencia. En mis pruebas, esto ayudó un poco más que comprobar la propiedad GlobalAssemblyCache.

Combiné ambos y lo conseguí aún más rápido. El código a continuación incluye ambos 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)

En el caso de Portable .NET limitaciones , el siguiente código debería 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;
    }

o para un gran número de ensamblajes que utilizan el rendimiento de rendimiento basado en estado de bucle :

    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 mejorar la respuesta de Andrew y convertir todo en una consulta LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top