¿Cómo se enumeran todas las clases con atributo de clase personalizado?

Pregunta basada en el ejemplo de MSDN .

Digamos que tenemos algunas clases C # con HelpAttribute en la 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 posibles opciones de menú, seleccionando el elemento traerá a la instancia de pantalla de dicha clase. El número de clases / ítems crecerá lentamente, pero de esta manera podemos evitar enumerarlos en otro lugar, creo.

Si, absolutamente. Usando la reflexión:

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

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

Desde allí, debería llamar a GetExportedTypes (si solo desea tipos públicos) o GetTypes en cada Assembly para obtener los tipos que están contenidos en el ensamblado.

Luego, debe llamar al método GetCustomAttributes en cada instancia de Type , pasando el tipo del atributo que desea buscar.

Puede usar LINQ para simplificar esto:

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

La consulta anterior obtendrá cada tipo con su atributo aplicado, junto con la instancia del atributo (s) asignado (s).

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

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

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

Y si el ensamblaje tiene una gran cantidad de tipos, puede 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() }; 

Otras respuestas hacen referencia a GetCustomAttributes . Agregar 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 encarecidamente almacenar 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 en todos los tipos en todos los ensamblajes 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 del rendimiento además de la solución aceptada. Iterando, aunque todas las clases pueden ser lentas porque hay tantas. A veces puede filtrar un conjunto completo sin mirar ninguno de sus tipos.

Por ejemplo, si está buscando un atributo que haya declarado usted mismo, no espera que ninguno de los archivos DLL del sistema contenga ningún tipo con ese atributo. La propiedad Assembly.GlobalAssemblyCache es una forma rápida de buscar DLL del sistema. Cuando probé esto en un progtwig real encontré que podía saltear 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 le importan ese ensamblaje y otros ensamblajes que lo hacen referencia. En mis pruebas, esto me ayudó un poco más que verificando 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 las limitaciones de .NET portátil , el siguiente código debería funcionar:

  public static IEnumerable 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 una gran cantidad de ensamblajes que usan yield return basado en bucle de estado:

  public static IEnumerable GetAtributedTypes( Assembly[] assemblies, Type attributeType ) { foreach (var assembly in assemblies) { foreach (var typeInfo in assembly.DefinedTypes) { if (typeInfo.IsDefined(attributeType, false)) { yield return typeInfo; } } } }