One year ago I've solved the same problem. So I'll explain how it works using my code as an example, so you will know how to design command line parsers and how to solve your problem.
As OwlSolo already said you need a base class or an interface which will be able to accept arguments and execute some logic.
In case of the Cmd.Net it's the Command class:
public abstract class Command
{
protected Command(string name);
public string Name { get; }
public abstract void Execute(ArgumentEnumerator args);
}
The class has two members:
- The property
Name which provides a name for the command
- The method
Execute which accepts arguments and do the business logic.
The ArgumentEnumerator splits the provided string like the string.Split method mentioned above in OwlSolo's code, but in a more complex way. It produces key-value pairs of the argument's name and its value.
As an example, you have a string which looks like this:
"/namedArg:value1 value2"
will be parsed into two pairs. The first pair is a named argument with the name "namedArg" and the value "value1". The second is an unnamed argument (name equals to string.Empty) with the value "value2".
The purpose of named arguments is to allow to rearrange them and make some of them optional. This should improve usability.
Now we want to have a collection of commands from which you can faster take one of them by the name. And the Dictionary<string, Command> is the best choice, but let's see further and make a command which will transfer control to a child command. So we will be able to build command categories/hierarchies like in netsh.
public sealed class CommandContext : Command
{
public CommandContext(string name);
public CommandCollection Commands { get; }
public override void Execute(ArgumentEnumerator args);
}
public sealed class CommandCollection : KeyedCollection<string, Command>
{
public CommandCollection()
: base(StringComparer.OrdinalIgnoreCase)
{
}
protected override string GetKeyForItem(Command item)
{
return item.Name;
}
public bool TryGetCommand(string name, out Command command)
{
return Dictionary.TryGetValue(name, out command);
}
}
The overridden Execute method will take the first argument if it's unnamed and search for the command using the TryGetCommand method. When it finds the command, it executes it with the all arguments except the first one. If there is no command found or the first argument has a name, then we should show an error.
NOTE Since we use StringComparer.OrdinalIgnoreCase we should not worry about character cases in the passed name.
Now it's time to think about automated argument parsing and converting. To achieve this we can use reflection and TypeConverters.
public sealed class DelegateCommand : Command
{
public DelegateCommand(Delegate method);
public Delegate Method { get; }
public override void Execute(ArgumentEnumerator args);
}
In the DelegateCommand's constructor you should gather information about method's parameters (names, default values, type converters, etc.) and later use that in the Execute method to cast and provide arguments to the method.
I omitted implementation details because it's complex, but you can read about that in DelegateCommand.cs and in Argument.cs.
Finally you will be able to execute methods without any parsing in it.
CommandContext root = new CommandContext(
"root",
new Command("multiply", new Action<int, int>(Multiplication)),
new CommandContext(
"somecontext",
// ...
)
);
ArgumentEnumerator args = new ("add /x:6 /y:7");
root.Execute(args);
public static void Multiplication([Argument("x")] int x, [Argument("y")] int y)
{
// No parsing, only logic
int result = x * y;
}