4

I have a public abstract class Client with two inheriting classes Customer and TimeWaster.

I created a drop-down menu on a C# Windows Forms that I would like to show these two class names as options: Customer & TimeWaster.

All I can think of is to create a simple List that contains these two terms, and then bind the list to the combobox DataSource:

List<string> clientType = new List<string>()
{
    "Customer",
    "TimeWaster"
};

public frmClientScreen()
{
    cmboxClientType.DataSource = clientType;
}

But this is not maintainable, because in the future I might add many other classes whose names I would like to be displayed in the drop-down menu.

How do I link the class names in my Visual Studio Solution to the items displayed by the combobox?

  • 1
    You are talking about "type discovery" and the way to go is reflection. Check out this question: http://stackoverflow.com/questions/2362580/discovering-derived-types-using-reflection – Ishmaeel Mar 17 '17 at 15:29
  • @Ishmaeel Thank you! type discovery & reflection are new to me, so I have to learn a whole new concept. Cheers –  Mar 17 '17 at 16:06

2 Answers2

2

To get the type name of a known set of types:

List<string> clientType = new List<string>()
{
    nameof(Customer),
    nameof(TimeWaster)
};

public frmClientScreen()
{
    cmboxClientType.DataSource = clientType;
}

As to dynamically getting all types derived from a specific type, this question shows an example of how to do that.

From the Accepted Answer at that question:

var listOfBs = (from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
                from assemblyType in domainAssembly.GetTypes()
                where typeof(B).IsAssignableFrom(assemblyType)
                select assemblyType).ToArray();

Where B should be replaced with your base class.

Then, after getting the list of derived types, you can do the following:

var clientTypes = listOfBs.Select(x => x.Name).ToList();
Community
  • 1
  • 1
Brandon Kramer
  • 1,098
  • 6
  • 7
  • Many thanks. I put this snippet in the `Main` just to see the output, but it doesn't return anything, with the array length coming back as zero. My classes are visible in my `Main` so I wonder if this code can be placed anywhere? –  Mar 17 '17 at 16:41
  • 1
    In main, many assemblies may not be loaded yet, since .Net uses Just in Time loading. That is, assemblies are not loaded until they are used. In main, most assemblies have likely not been loaded yet, so it is not surprising that you get an empty array. – Brandon Kramer Mar 17 '17 at 17:04
1

I've had to do this a number of different times, to the point where I just wanted one global function that I could use. So I cleaned up some commonly sourced code and ended up:

public static class ReflectionHelper
{
    public static List<T> GetAllNonabstractClassesOf<T>()
    {
        Object[] args = new Object[0];
        return GetAllNonabstractClassesOf<T>(args);
    }

    public static List<T> GetAllNonabstractClassesOf<T>(Object[] args)
    {
        List<T> retVal = new List<T>();
        IEnumerable<object> instances = from t in Assembly.GetExecutingAssembly().GetTypes()
                                        where t.IsSubclassOf(typeof(T)) && !t.IsAbstract
                                        select Activator.CreateInstance(t, args) as object;
        foreach (T instance in instances)
        {
            retVal.Add(instance);
        }
        return retVal;
    }
}

... and then, you can simply call the code like this:

List<myClass> = ReflectionHelper.GetAllNonabstractClassesOf<myClass>();

(or if your class has required constructor arguments, you can use the second function.)

Anyway, the beauty of it is, you only need this function once through your code, no matter how many different classes you want to reflect through.

Kevin
  • 2,133
  • 1
  • 9
  • 21
  • This is genius! Thank you very much. –  Mar 17 '17 at 16:41
  • 1
    Note that this will only load classes that are in the current assembly. If you define a child class of your base class in a different assembly, then it will not be retrieved by this method. – Brandon Kramer Mar 17 '17 at 17:05