2011-03-23 22 views
79

que tengo una clase abstracta:Obtener todas las clases heredadas de una clase abstracta

abstract class AbstractDataExport 
{ 
     public string name; 
     public abstract bool ExportData(); 
} 

tengo clases que se derivan de AbstractDataExport:

class XmlExport : AbstractDataExport 
{ 
    new public string name = "XmlExporter"; 
    public override bool ExportData() 
    { 
     ... 
    } 
} 
class CsvExport : AbstractDataExport 
{ 
    new public string name = "CsvExporter"; 
    public override bool ExportData() 
    { 
     ... 
    } 
} 

¿Es posible hacer algo como esto? (Pseudocódigo :)

foreach (Implementation imp in Reflection.GetInheritedClasses(AbstractDataExport) 
{ 
    AbstractDataExport derivedClass = Implementation.CallConstructor(); 
    Console.WriteLine(derivedClass.name) 
} 

con una salida como

CsvExporter 
XmlExporter 

?

La idea detrás de esto es simplemente crear una nueva clase que se derive de AbstractDataExport para que pueda iterar a través de todas las implementaciones automáticamente y agregar, por ejemplo, los nombres a una lista desplegable. Solo quiero codificar la clase derivada sin cambiar nada más en el proyecto, recompilar, ¡bingo!

Si tiene soluciones alternativas: dígaselo.

Gracias

Respuesta

116

Este es un problema tan común, especialmente en aplicaciones de GUI, que estoy sorprendido de que no haya un Clase de BCL para hacer esto de la caja. Así es como lo hago.

public static class ReflectiveEnumerator 
{ 
    static ReflectiveEnumerator() { } 

    public static IEnumerable<T> GetEnumerableOfType<T>(params object[] constructorArgs) where T : class, IComparable<T> 
    { 
     List<T> objects = new List<T>(); 
     foreach (Type type in 
      Assembly.GetAssembly(typeof(T)).GetTypes() 
      .Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(T)))) 
     { 
      objects.Add((T)Activator.CreateInstance(type, constructorArgs)); 
     } 
     objects.Sort(); 
     return objects; 
    } 
} 

Unas pocas notas:

  • No se preocupe por el "coste" de esta operación - que sólo va a hacer una vez (con suerte) e incluso entonces no es tan lenta como pensarías
  • Necesita usar Assembly.GetAssembly(typeof(T)) porque su clase base podría estar en un ensamblaje diferente.
  • Debe usar los criterios type.IsClass y !type.IsAbstract porque arrojará una excepción si intenta crear una instancia de una interfaz o clase abstracta.
  • Me gusta forzar las clases enumeradas para implementar IComparable para que puedan ser ordenadas.
  • Sus clases secundarias deben tener firmas de constructor idénticas, de lo contrario arrojará una excepción. Esto típicamente no es un problema para mí.
+1

¿Puede un tipo no ser abstracto y no ser de clase al mismo tiempo? – user2341923

+1

¡Esto es una locura! No tenía idea de que pudieras hacer cosas como esta con reflexión. – CanadaIT

+0

@ user2341923 una enumeración? – Cesar

10

Puede que no sea la manera elegante, pero se puede repetir todas las clases en el montaje e invocar Type.IsSubclassOf(AbstractDataExport) para cada uno.

+1

+1: Creo que esa es prácticamente la única solución. –

+0

¡Muchas gracias! Mi solución se basa en tus sugerencias. – trampi

3

typeof(AbstractDataExport).Assembly le indica un conjunto en el que se encuentran sus tipos (suponiendo que todos estén en el mismo).

assembly.GetTypes() le ofrece todos los tipos en ese conjunto o assembly.GetExportedTypes() le da tipos que son públicos.

Iterar a través de los tipos y usar type.IsAssignableFrom() le indica si se deriva el tipo.

+0

Gracias por su respuesta, assembly.gettypes() era algo que necesitaba para la solución. – trampi

34

, asumiendo que están todos definidos en el mismo conjunto, se puede hacer:

IEnumerable<AbstractDataExport> exporters = typeof(AbstractDataExport) 
    .Assembly.GetTypes() 
    .Where(t => t.IsSubclassOf(typeof(AbstractDataExport)) && !t.IsAbstract) 
    .Select(t => (AbstractDataExport)Activator.CreateInstance(t)); 
+1

Gracias, muy interesante. Es necesario echar un vistazo más de cerca a 'Activator.CreateInstance' – trampi

+3

Use' t.GetConstructor (Type.EmptyTypes)! = Null' como una restricción adicional para evitar intentar crear instancias de clases que no tengan un constructor sin parámetros. – mrexodia

0

Bueno, la reflexión es su amigo. Antes de implementar eso, es posible que desee considerar los aspectos de rendimiento de eso: "La reflexión siempre es costosa" :-)

+1

¡Gracias por su respuesta! ¿Me puede dar más información sobre qué tan lenta será la ejecución? Cualquier cosa para comparar? :) – trampi

Cuestiones relacionadas