web-dev-qa-db-fra.com

Obtenir toutes les classes héritées d'une classe abstraite

J'ai un cours abstrait:

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

J'ai des classes qui sont dérivées 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()
    {
        ...
    }
}

Est-il possible de faire quelque chose comme ça? (Pseudocode :)

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

avec une sortie comme

CsvExporter
XmlExporter

?

L'idée est simplement de créer une nouvelle classe dérivée de AbstractDataExport pour pouvoir parcourir automatiquement toutes les implémentations et ajouter par exemple les noms à une liste déroulante. Je veux juste coder la classe dérivée sans rien changer d'autre dans le projet, recompiler, bingo!

Si vous avez des solutions alternatives: dites-leur.

Merci

104
trampi

C'est un problème tellement courant, en particulier dans les applications à interface graphique, que je suis surpris qu'il n'y ait pas de classe BCL pour le faire immédiatement. Voici comment je le fais.

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

Quelques notes:

  • Ne vous inquiétez pas du "coût" de cette opération - vous ne le ferez qu'une fois (espérons-le) et même dans ce cas, ce n'est pas aussi lent que vous le pensez.
  • Vous devez utiliser Assembly.GetAssembly(typeof(T)) parce que votre classe de base peut appartenir à un autre assembly.
  • Vous devez utiliser les critères type.IsClass Et !type.IsAbstract, Car une exception sera levée si vous essayez d'instancier une classe abstraite ou une interface.
  • J'aime forcer les classes énumérées à implémenter IComparable afin qu'elles puissent être triées.
  • Vos classes enfants doivent avoir des signatures de constructeur identiques, sinon une exception sera générée. Ce n'est généralement pas un problème pour moi.
155
Repo Man

En supposant qu'ils soient tous définis dans le même assemblage, vous pouvez faire:

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

Ce n'est peut-être pas une façon élégante, mais vous pouvez parcourir toutes les classes de l'assembly et appeler Type.IsSubclassOf(AbstractDataExport) pour chacune d'elles.

11
WorldIsRound

typeof(AbstractDataExport).Assembly vous indique un assemblage dans lequel vos types sont situés (en supposant qu'ils soient tous identiques).

Assembly.GetTypes() vous donne tous les types de cet assemblage ou Assembly.GetExportedTypes() vous donne les types qui sont publics.

En parcourant les types et en utilisant type.IsAssignableFrom(), vous pouvez déterminer si le type est dérivé.

3