0

For a long time I have been struggling with finding a way to dynamically instantiate all classes that extends a specific baseclass (during runtime). From what I have read, it is supposed to be done using Reflection, unfortunately I haven't figured out how yet.

My project structure looks like this:

Library
--|
  |
  --Vehicle.cs (abstract class)
  |
  --Car.cs (extending vehicle)
  |
  --Bike.cs (extending vehicle)
  |
  --Scooter.cs (extending vehicle)
  |
  --InstanceService.cs (static class)
  |
  |
ConsoleApplication
--|
  |
  --Program.cs

The InstanceService class contains a generic method which is supposed to return an IEnumerable<T> containing the instantiated classes extending Vehicle, meaning Car, Bike & Scooter.

The code posted bellow is the current state of the InstanceService class, after having tried a ton of different solutions, meaning it mostly contains tools for debugging.

InstanceService.cs

using System;
using System.Collections.Generic;

namespace Library
{
    public static class InstanceService<T>
    {
        //returns instances of all classes of type T
        public static IEnumerable<T> GetInstances()
        {

            var interfaceType = typeof(T);
            List<T> list = new List<T>();
            Console.WriteLine("Interface type: " + interfaceType.ToString());
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach(var assembly in assemblies)
            {
                Console.WriteLine("Assembly: " + assembly.ToString());
                if (assembly.GetType().IsAbstract)
                {
                    var instance = (T) Activator.CreateInstance(assembly.GetType());
                    list.Add(instance);
                }
            }
            return list;
        }
    }
}

I have also attached the code for the abstract Vehicle class as well as one of the implementations of it.

Vehicle.cs

namespace Library
{
    public abstract class Vehicle
    {
        protected float maxSpeedInKmPerHour;
        protected float weightInKg;
        protected float priceInDkk;
    }
}

Car.cs

namespace Library
{
    public class Car : Vehicle
    {

        public Car()
        {
            this.maxSpeedInKmPerHour = 1200;
            this.weightInKg = 45000;
            this.priceInDkk = 71000000;
        }
    }
}
  • What is it that is (specifically) going wrong with this current approach? – StayOnTarget Sep 04 '18 at 16:54
  • Nothing specific, atleast it is not returning any errors. I am just curios as to how this is achieved in the best possible way. Also, with the current approach, the function only seems to be returning a single object which cannot be looped through, when in fact it should return a List containing 3 instantiated classes derived from `Vehicle`. Then I use `ToString()` on the list returned by the function, I get the following output `System.Collections.Generic.List1[Library.Vehicle]` – Jonas Mohr Pedersen Sep 04 '18 at 17:10

2 Answers2

1

I think the method that should interest you is IsAssignableFrom.

Also, the code is much easier with LINQ, if you're allowed to use that, and since you're creating the objects one at a time, I suggest using yield return.

static IEnumerable<T> GetInstances<T>() 
{
    var baseType = typeof(T);
    var types = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany( a => a.GetTypes() )
        .Where
        (
            t => baseType.IsAssignableFrom(t)                  //Derives from base
              && !t.IsAbstract                                 //Is not abstract
              && (t.GetConstructor(Type.EmptyTypes) != null)   //Has default constructor
        );


    foreach (var t in types)
    {
        yield return (T)Activator.CreateInstance(t);
    }
}

Or if for some reason you're showing off and you want to do it with one statement:

    var types = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany( a => a.GetTypes() )
        .Where
        (
            t => typeof(T)IsAssignableFrom(t)  
              && !t.IsAbstract 
              && (t.GetConstructor(Type.EmptyTypes) != null) 
        )
        .Select
        (
            t => (T)Activator.CreateInstance(t)
        );
John Wu
  • 50,556
  • 8
  • 44
  • 80
0

This should work for any type that can be instantiated using a default constructor. The fact that your classes are derived from another class is beside the point, unless I'm missing something...

private T MakeInstance<T>()
{
    // the empty Type[] means you are passing nothing to the constructor - which gives
    // you the default constructor.  If you need to pass in an int to instantiate it, you
    // could add int to the Type[]...
    ConstructorInfo defaultCtor = typeof(T).GetConstructor(new Type[] { });

    // if there is no default constructor, then it will be null, so you must check
    if (defaultCtor == null)
        throw new Exception("No default constructor");
    else
    {
        T instance = (T)defaultCtor.Invoke(new object[] { });   // again, nothing to pass in.  If the constructor needs anything, include it here.
        return instance;
    }
}
Dave Smash
  • 2,941
  • 1
  • 18
  • 38
  • But with this approach you only seem to be creating one instance? I suppose the approach is a bit different when I am creating instances of all classes derived from the baseclass? – Jonas Mohr Pedersen Sep 04 '18 at 17:07
  • Yes, this creates one instance. If you want more than one instance of a specific type, you can run it in a loop and add them to a collection as they are instantiated. I'm not quite sure what you're going for here - if you want a new one of each, then John Wu's approach would work better, but unless you are constantly adding classes, I would just instantiate one of each manually and avoid using reflection and generics like InstanceService (which are both designed for cases when you DON'T know the data type you want at compile time - it sounds like you DO know up front) – Dave Smash Sep 04 '18 at 18:48
  • Yes, that is correct. However it is unrelated what I want, as this is an assignment, and not an actual application. And in the assignment the following is specified: _Create an InstanceService with the method_ `IEnumerable GetInstances()` _which returns an instance of all classes of type T_ – Jonas Mohr Pedersen Sep 04 '18 at 19:07