Reputation: 5177
I'm writing a command line utility in C# for .NET Core. I want to allow the user to specify the "action" to run based on a command line parameter. I'm mimicking the PowerShell style command line options, so one of my options is /Action
. So for example, the user might call the app with /Action:Update
or /Action:Reset
.
In C#, I have a method for each action that follows a specific method signature. So, for the Update
method above, I have a method like this: public static int Update(Dictionary<string,string> cmdLineArgs, SomeObject obj)
. Each method that is related to a valid parameter on /Action
has exactly the same signature (same types and variable names).
Right now I just have a switch
block to call the actions, but this seems incredibly inefficient:
int returnValue;
switch (parsedArgs["action"]) {
case "update":
returnValue = Update(parsedArgs, o);
break;
case "reset":
returnValue = Reset(parsedArgs, o);
break;
...
default:
returnValue=255;
Console.WriteLine($"No such action {parsedArgs["action"]}.");
break;
}
I've used attributes in the context of Web APIs, and they seem to be a natural starting point to make this more generic. This would ideally result in the situation where adding a new action is as simple as writing its method and adding the correct attribute with the name the user can call it by in the /Action
switch. My idea is to create a custom attribute (say, AppActionName
) and then put that attribute on any method that can be called as an action from the command prompt:
[AppActionName("update")]
public static int Update(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...
[AppActionName("reset")]
public static int Reset(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...
An alternative I've thought about, which would take advantage of type safety, would be to use an interface that defines the action method:
public interface IAppAction
{
int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj);
}
[AppActionName("update")]
public class UpdateAction : IAppAction
{
public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...
[AppActionName("reset")]
public class ResetAction : IAppAction
{
public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...
In either case though, what I'm not sure of is how to actually search for, instantiate and run the method.
In the first option (putting the AppActionName directly on the method), I see two problems: 1) having to figure out how to search all methods in the assembly for those with the given attribute, filtering, and then how to actually call the method, and 2) unless I don't know how to do it, I don't think I can enforce the proper method signature using this method.
int returnValue;
// in other languages you can get a variable and then call it, but this isn't other languages
// but you might do something like: myMethod = findMethodWithAttribute("update"); returnValue=myMethod(parsedArgs, o);
The second option (interface on class implementing interface) seems more type-safe and should be easier to implement (declare an interface-variable and then have it assigned to an instance of the correct class), but I'm still not sure how to actually search for the attribute with the correct name.
int returnValue;
// how would you do this correctly?
IAppAction appActionClass = new FindTheClassWithTheAttributeWithParameter("update")();
returnValue = appActionClass.Run(parsedArgs, o);
So i think the essence of my question is: "how do I find which method/class has the attribute I defined with the parameter I specified, and then how do I actually instantiate/call the result?"
Upvotes: 3
Views: 1129
Reputation: 32068
The second approach should generally be easier to deal with.
I made an example (which you can run here) for getting the classes that implement the interface and their attribute.
Given something like this:
public class AppActionNameAttribute : Attribute
{
public string Action { get; set; }
public AppActionNameAttribute(string action) { Action = action; }
}
public interface IAppAction
{
int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj);
}
[AppActionName("update")]
public class UpdateAction : IAppAction
{
public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj)
{
Console.WriteLine("Handled :)");
return 1;
}
}
public class SomeObject { }
You can do this:
var handlers = typeof(Program).Assembly
// Get all types in the assembly
.GetExportedTypes()
// that are classes and implement IAppAction
.Where(x => x.IsClass && x.GetInterface("IAppAction") != null)
.Select(x => new
{
// assuming they are always decorated with [AppActionName]
Action = x.GetCustomAttribute<AppActionNameAttribute>().Action,
// get a new instance, assuming parameterless constructor
Handler = (IAppAction)Activator.CreateInstance(x)
})
// and convert it to a Dictionary that you can easily use
.ToDictionary(x => x.Action, x => x.Handler);
Which you can store (preferably in some static field as you don't want to be running this often) and simply use like this:
private static Dictionary<string, IAppAction> _handlers = ...;
public static void Main()
{
string action = Console.ReadLine();
// you should check the action actually exists in the Dictionary
var handler = _handlers[action];
// and then run it:
Console.WriteLine(handler.Run(someData, otherData));
}
Upvotes: 2