2

In C# is it possible, without reflection, to instantiate all class found in a namespace?

I have a list of script class. All in same folder. All implementing my base class BaseScript. I need to run all of them every hours. I already have the time and events. I also already have a List<BaseScript> ListOfScripts. But now I'm doing this:

    public class Program
    {
        private static List<BaseScript> ListOfScrpits { get; set; }

        static void Main(string[] args)
        {
            ListOfScrpits = new List<BaseScript>
            {
                new DoThis(),
                new DoThat(),
                new DoSomethingElse()
            };

            Timer timer = new Timer();
            timer.Interval = 600000; // 10 * 60 seconds 
            timer.Elapsed += new ElapsedEventHandler(OnTimer);
            timer.Start();
        }
        private static void OnTimer(object sender, ElapsedEventArgs e)
        {
            // TODO
        }
    }

Can I create my list of scripts dynamically?

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
  • 1
    Does this answer your question? [How can I get all classes within a namespace?](https://stackoverflow.com/questions/949246/how-can-i-get-all-classes-within-a-namespace). Once you do this, simply use `Activator.CreateInstance` – Frontear Jan 07 '20 at 02:34
  • 3
    I guess you missed the *"without reflection"* part. ;) @Bastien, for the record, why not reflection? – Andrew Jan 07 '20 at 02:36
  • Nice catch, I did miss that. I don't actually know if that's possible without any form of reflection. – Frontear Jan 07 '20 at 02:38
  • I would imagine this would be do-able post build with some sort of IL weaver - maybe there is something in the Fody family for stuff that might help? Cecil can be useful in some cases over reflection too, as it doesn't need to load the assembly to get the metadata. Maybe you could reverse the relationship and do it more conventionally? – Élie Jan 07 '20 at 02:41
  • 1
    @Andrew Because for real all the scripts I want to load can be located in many different folders, will probably be in same namespace, but for sure MUST all implement the same BaseClass. This is the only information I'm really sure. I don't want to do something too complicate. If not possible I can look for another way. – Bastien Vandamme Jan 07 '20 at 02:47
  • 3
    That doesn't really explain why you can't use reflection. You could find all types in that namespace assignable to the `BaseClass` type (excluding abstract, etc.) and create instances of them with reflection. Without reflection, I'd assume that each class would need a static constructor so as to register itself with some central list, or for there to be a pre-build step to find all the classes and add them to your list (i.e. dynamic code generation). – ProgrammingLlama Jan 07 '20 at 02:48
  • As do I - I just tested the static constructor self-registering scripts and it's a no-go. The static constructor isn't necessarily called until you access the individual script class, so registration isn't guaranteed to be called. Reflection (as in my answer) is probably the best way to do this. – ProgrammingLlama Jan 07 '20 at 03:05
  • @BastienVandamme If you have specific requirements that prevent you from using reflection that’s one thing. But you shouldn’t let a lack of knowledge in a particular area prevent you from using a useful tool. By avoiding the use of reflection you’re essentially reinventing the wheel Instead of standing on the shoulders of those who have come before. More experienced devs than likely you or I who have spent thousands of hours developing and testing all the classes in the reflection API as opposed to whatever a handful of us on SO can cook up in a few days. – Imbaker1234 Jan 07 '20 at 13:43

2 Answers2

2

I'd suggest using a Dependency Injection container here to handle the instantiation of the classes for you. The benefit of using DI here is that it allows your classes to be much more extensible. For example, if you end up needing some other dependencies in any of your classes implementing BaseScript, the DI container will handle resolving those for you so long as they're registered.

You can do this with pretty easily with the ServiceCollection available in .NET Core:

class Program
{
    private static List<BaseScript> ListOfScrpits { get; set; }

    static void Main(string[] args)
    {
        ServiceProvider serviceProvider = new ServiceCollection()
            .AddTransient<BaseScript, DoThis>()
            .AddTransient<BaseScript, DoThis>()
            .AddTransient<BaseScript, DoSomethingElse>()
            .BuildServiceProvider();

        ListOfScrpits = serviceProvider.GetServices<BaseScript>().ToList();

        foreach (BaseScript script in ListOfScrpits)
            Console.WriteLine(script.GetType().Name);

        Console.ReadLine();
    }
}

You can even register all instances of your BaseScript (using Reflection as seen in other answers) so that new implementations will be automatically resolved and used as you add them. Just change the serviceProvider bit to:

System.Reflection.Assembly[] assemblies = new[] { System.Reflection.Assembly.GetExecutingAssembly() };
IEnumerable<Type> scriptTypes = assemblies
    .SelectMany(a => a.GetTypes())
    .Where(t => t != typeof(BaseScript) && typeof(BaseScript).IsAssignableFrom(t) && !t.IsAbstract);

ServiceCollection serviceCollection = new ServiceCollection();
foreach (Type scriptType in scriptTypes)
    serviceCollection.AddTransient(typeof(BaseScript), scriptType);

ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();

A working example can be seen here: https://dotnetfiddle.net/xBqsNg

devNull
  • 3,849
  • 1
  • 16
  • 16
  • 1
    Additionally, some DI containers like Autofac have registation methods which handle most of this discovery and registration for you. – ProgrammingLlama Jan 07 '20 at 03:28
1

Based on your requirements, and your latest comment suggesting you would accept reflection, I suggest the following:

// add any assemblies you want to discover scripts in here
var assemblies = new[] { System.Reflection.Assembly.GetExecutingAssembly() }; 

var scriptTypes = assemblies
    // get the declared types for each and every assembly
    .SelectMany(a => a.GetTypes())
    // filter so that we ignore BaseClass, only accept types assignable to base class,
    // don't accept abstract classes and only accept types with a parameterless constructor
    .Where(t => t != typeof(BaseClass) && typeof(BaseClass).IsAssignableFrom(t) && !t.IsAbstract && t.GetConstructors().Any(c => c.GetParameters().Length == 0));

// create an instance of each type and construct a list from it
var scripts = scriptTypes
    .Select(t => (BaseClass)Activator.CreateInstance(t))
    .ToList();
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • @AlexeiLevenkov https://www.infoq.com/news/2008/05/CSharp-var/ Never use var unless it's absolutely necessary, such as long class names. – Krythic Jan 07 '20 at 03:13
  • 4
    @Alexei I think the use of `var` is something that is very polarising for some people. Some see it as the devil, some see it as a blessing. I think it's relatively clear from reading my code that `assemblies` is `Assembly[]`, `scriptTypes` is `IEnumerable` and `scripts` is `List` without needing to explicitly write those. Everyone is entitled to their own opinion. I feel downvoting is harsh in this case, but I might equally downvote in a similar way for a similar thing on another question. Each to their own. – ProgrammingLlama Jan 07 '20 at 03:13
  • 5
    @Krythic this is just your own opinion and style of coding, downvoting based on that makes no sense – Dmitri Jan 07 '20 at 03:15
  • @John_ReinstateMonica The 1-5 Seconds I need to figure out what var is, is 1-5 seconds I could have been coding. Usage also greatly reduces maintainability. – Krythic Jan 07 '20 at 03:15
  • 3
    @Krythic FWIW If it's unclear what a method returns, then I would agree that using `var` is a hinderance rather than a convenience, but generally we should strive to write code that's clear. You wouldn't call a method `GetBlahDeBlah` if it returns a `User`. You'd call it `GetUserById` or something. Then `var user = GetUserById(id);` would make perfect sense to anyone reading it: the variable is named `user` and the method is called `GetUserById`. I would be surprised if such a method returned anything other than a `User`. In this case, I believe the variable names are sufficiently descriptive. – ProgrammingLlama Jan 07 '20 at 03:16
  • 2
    @Krythic "Never use var unless it's absolutely necessary, such as long class names" is no more a recommendation for ... I don't remember. This page is from 2008. Atually with new way of coding and respect of rule to KISS the usage of var is recommended and logic. Just name your variables properly. I force myself to use var to force me to use good name for my variables. I always finish all my variable name with the type with some exception like int, string, ... – Bastien Vandamme Jan 07 '20 at 03:38