Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically invoke a class based on a string?

Can you give me some guidance to dynamically load class based on name. The name is provided as a argument at runtime.

    static void Main(string[] args) 
    {
        ...
    } 

Let's say my argument is "parrot" then I would like to load my parrotProcessor class. If my argument name is "snake" then I load my To make snakeProcessor. Of course this mean I have a parrot and snake processor class that inherit an interface IProcessor. I don't know what could be the list of all processor. this list is maintained by other developers and they can create what they want.

Example of my processor interface:

    public interface IProcessor
    {
        void Process();
    }

And in my Program.cs

    static void Main(string[] args) 
    {
        var processor = GetProcessor(args[0]);
        processor.Process();
    } 

My question is what should I do in GetProcessor() method?

Here is what I have at this moment:

    private IProcessor GetProcessor(string name)
    {
        switch (name)
        {
             case "ant":
                return new AntProcessor();
             case "parrot":
                return new ParrotProcessor();
             case "snake":
                return new SnakeProcessor();
             default:
                throw new ArgumentException("Case not found");
        }
    }

But this mean I must updated this ugly switch each time I create a new processor specialized class. This is not possible. I can do it now for development but not for long term.

What are the solutions? Should I use something similar to DI with NInject or am I mixing everything? Can I simply use the Invoke method? And why Invoke or DI? I know this question is kind of open question but it is something that happen often in code of many people so I suppose there is unique best practice about it.

like image 222
Bastien Vandamme Avatar asked Dec 17 '19 07:12

Bastien Vandamme


3 Answers

You can use something like the following

var type = Type.GetType("MyFullyQualifiedTypeName");
var myObject = (MyAbstractClass)Activator.CreateInstance(type);

You need to do some string modifications like taking the word, adding the processor string and making sure that all the processors are at the same place.

If you are certain that the type is in the current assembly, simply fully qualified name by this

 Activator.CreateInstance(Type.GetType("SomeProcessor"));
like image 56
Athanasios Kataras Avatar answered Oct 16 '22 12:10

Athanasios Kataras


First of all, there is absolutely nothing wrong with using a switch block as you are doing. It gets the job done, and it serves as an explicit source of truth with regards to the interpretation of the command-line parameter. It also acts as a whitelist, preventing users from passing a class name that maybe you don't want them to instantiate. Yes, you have to update it every time you add a new class, but you're already adding the class itself, so you are already in a situation where a new version and deployment will be needed.

That being said, there are various reasons why you'd rather have the ability to look up a command line parameter and automatically know which type to instantiate. That is trivial to do with a Dictionary<string,Func<IProcessor>>:

this.Map = new Dictionary<string,Func<IProcessor>>
{
    { "ant", () => new AntProcessor() },
    { "snake", () => new SnakeProcessor() },
    { "parrot", () => new ParrotProcessor() },
    { "lizard", () => new LizardProcessor() }
};

Then you would handle a command-line parameter like this:

//Use the string to look up a delegate
var ok = this.Map.TryGetValue(textFromCommandline, out var func);  

//If not found, user entered a bad string
if (!ok) throw new ArgumentException();

//Invoke the delegate to obtain a new instance 
IProcessor processor = func(); 

return processor;

Once you understand the concept of using a map in this way, you can come up with a scheme to populate it automatically, e.g

this.Map = assembly.GetTypes()
    .Where( t => typeof(IProcessor).IsAssignableFrom( t ))
    .ToDictionary
    (
        t => t.Name, 
        t => new Func<IProcessor>( () => Activator.CreateInstance(t) as IProcessor );
    );

And you will have met your goal of not having to maintain a hardcoded list anywhere.

like image 45
John Wu Avatar answered Oct 16 '22 14:10

John Wu


You can rewrite private IProcessor GetProcessor(string name) method using a little mix of reflection and LINQ like this:

 private IProcessor GetProcessor<Tinterface>(string name) where Tinterface : IProcessor
 {
      var type = typeof(Tinterface).Assembly.GetTypes()
                                   .First(x => x.FullName.Contains("name"));
      return (Tinterface)Activator.CreateInstance(type);
 }

Usage:

static void Main(string[] args) 
{
    var processor = GetProcessor<IProcessor>(args[0]);
    processor.Process();
}

This route saves you the stress of typing the fully qualified name of the class

like image 2
Ndubuisi Jr Avatar answered Oct 16 '22 14:10

Ndubuisi Jr