158

I have an abstract class:

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

I have classes which are derived from 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()
    {
        ...
    }
}

Is it possible to do something like this? (Pseudocode:)

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

with an output like

CsvExporter
XmlExporter

?

The idea behind this is to just create a new class which is derived from AbstractDataExport so i can iterate through all implementations automatically and add for example the names to a Dropdown-List. I just want to code the derived class without changing anything else in the project, recompile, bingo!

If you have alternative solutions: tell em.

Thanks

gunr2171
  • 16,104
  • 25
  • 61
  • 88
trampi
  • 2,214
  • 4
  • 17
  • 17

4 Answers4

229

This is such a common problem, especially in GUI applications, that I'm surprised there isn't a BCL class to do this out of the box. Here's how I do it.

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

A few notes:

  • Don't worry about the "cost" of this operation - you're only going to be doing it once (hopefully) and even then it's not as slow as you'd think.
  • You need to use Assembly.GetAssembly(typeof(T)) because your base class might be in a different assembly.
  • You need to use the criteria type.IsClass and !type.IsAbstract because it'll throw an exception if you try to instantiate an interface or abstract class.
  • I like forcing the enumerated classes to implement IComparable so that they can be sorted.
  • Your child classes must have identical constructor signatures, otherwise it'll throw an exception. This typically isn't a problem for me.
Jacobs Data Solutions
  • 4,850
  • 4
  • 33
  • 38
  • 1
    Can a type be not-abstract and non-class at the same time? – user2341923 Sep 11 '14 at 19:14
  • 1
    This is absolutely insane! I had no idea you could do stuff like this with reflection. –  Feb 23 '16 at 05:01
  • @user2341923 an enum? – Cesar Oct 23 '16 at 10:32
  • 4
    What if the sub-class could be defined in a different assembly? – tobriand Nov 18 '16 at 10:00
  • 7
    @tobriand This will search all assemblies: AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(.... – Brain2000 Aug 24 '18 at 20:34
  • 1
    I've used that a couple of times in the last few years... does sometimes catch me out though when there's a reference that's managed to creep through without some of its dependencies. But yes, it grabs subtypes :). Worth noting that `GetAssemblies()` get's the *loaded* assemblies, not the *referenced* ones, from memory. This can sometimes be a pain. – tobriand Aug 25 '18 at 19:41
  • 1
    How do I bookmark this!!! – Michael Nov 26 '18 at 02:54
80

Assuming they are all defined in the same assembly, you can do:

IEnumerable<AbstractDataExport> exporters = typeof(AbstractDataExport)
    .Assembly.GetTypes()
    .Where(t => t.IsSubclassOf(typeof(AbstractDataExport)) && !t.IsAbstract)
    .Select(t => (AbstractDataExport)Activator.CreateInstance(t));
Cesar
  • 2,059
  • 25
  • 30
Lee
  • 142,018
  • 20
  • 234
  • 287
  • 2
    Thanks, very interesting. Need to take a closer look on `Activator.CreateInstance` – trampi Mar 23 '11 at 21:45
  • 6
    Use `t.GetConstructor(Type.EmptyTypes) != null` as an additional constraint to prevent trying to instantiate classes that don't have a parameterless constructor. – mrexodia Nov 02 '16 at 19:02
13

It may not be the elegant way but you can iterate all classes in the assembly and invoke Type.IsSubclassOf(AbstractDataExport) for each one.

WorldIsRound
  • 1,544
  • 10
  • 15
5

typeof(AbstractDataExport).Assembly tells you an assembly your types are located in (assuming all are in the same).

assembly.GetTypes() gives you all types in that assembly or assembly.GetExportedTypes() gives you types that are public.

Iterating through the types and using type.IsAssignableFrom() gives you whether the type is derived.

František Žiačik
  • 7,511
  • 1
  • 34
  • 59