0

I am working with a group to make a command line tool that takes a lot of sub commands - e.g.:

cmdtool.exe foo 1 2 3

or

cmdtool.exe bar 9 zombies

Currently it is implemented with a long and ugly list of

else if (command == "foo")
{
    if (argLength < 3)
    {
        Console.Error.WriteLine("Error: not enough args for foo");
        errorCode = ExitCode.Error;
    }
    else
    {
        // Do something useful
    }
}
else if (command == "bar")
// repeat everything from foo

My issue with that is that there is a lot of duplicate code and no good overview of all our subcommands
I know how I would do it in python:

def commandFoo(args):
    return DoFooStuff(args)

def commandBar(args):
    return DoBarStuff(args)

REGULAR_COMMANDS = {
    "foo": [1, 3, commandFoo],
    "bar": [2, 2, commandBar]
}

def evaluateCommand(cmdString, args):
    if cmdString not in REGULAR_COMMANDS:
        print("Unknown command:", cmdString)
        return False
    lower = REGULAR_COMMANDS[cmdString][0]
    upper = REGULAR_COMMANDS[cmdString][1]
    if not lower <= len(args) <= upper:
        print("Wrong number of args for:", cmdString)
        return False
    func = REGULAR_COMMANDS[cmdString][2]
    return func(args)

But how do I do that nicely in C#?

I have come close with this:

private static int CommandFoo(string[] args)
{
    Console.Error.WriteLine("FooFOO");
    return (int)ExitCode.Success;
}
private static int CommandBar(string[] args)
{
    Console.Error.WriteLine("BarBAR");
    return (int)ExitCode.Error;
}
private static Dictionary<string, List<object>> m_dCommandFuncs = new Dictionary<string, List<object>> {
    { "foo", {1, 3, CommandFoo}},
    { "bar", {2, 2, CommandBar}}
};

But the syntax for initializing my lookup table is not correct and when I try to correct it, it gets ugly and still doesn't compile.

Is this a limitation in the C# syntax? - that I cannot initialize dictionaries and lists in a pretty way?

How would a C# expert go about this?

Complete Solution as given by @mcbr :

delegate int Command(string[] args);

private static int CommandFoo(string[] args)
{
    Console.Error.WriteLine("FooFOO");
    return (int)ExitCode.Success;
}
private static int CommandBar(string[] args)
{
    Console.Error.WriteLine("BarBAR");
    return (int)ExitCode.Error;
}

private static Dictionary<string, List<object>> m_dCommandFuncs = new Dictionary<string, List<object>> {
    { "foo", new List<object>{1, 3, (Command) CommandFoo}},
    { "bar", new List<object>{2, 2, (Command) CommandBar}}
};


if (m_dCommandFuncs.ContainsKey(command))
{
    List<object> lLimitsAndFunc = m_dCommandFuncs[command];
    int lowerLimit = (int)lLimitsAndFunc[0];
    int upperLimit = (int)lLimitsAndFunc[1];
    Command commandFunc = (Command) lLimitsAndFunc[2];
    if (argLength < lowerLimit || argLength > upperLimit)
    {
        Console.Error.WriteLine("error: {0}, wrong number of arguments", command);
        exitCode = (int)ExitCode.Error;
    }
    else
    {
        var segment = new ArraySegment<string>(args, 1, (1 + upperLimit - lowerLimit));
        exitCode = commandFunc(segment.ToArray<string>());
    }
}

I also looked into various Nuget packages, but they add more clutter than benefits IMHO

Holger Bille
  • 2,421
  • 1
  • 16
  • 20
  • 2
    `"I am working with a group to make a command line tool that takes a lot of sub commands"` <= Have you already looked for an existing tool / wrapper? There are plenty available, just search NuGet. See this previous question on [so] : [Best way to parse command line arguments in C#?](https://stackoverflow.com/q/491595/1260204) – Igor Mar 21 '18 at 16:02
  • 1
    I'd use an arguments parser, such as [NDesk.Options](http://www.ndesk.org/Options) – spender Mar 21 '18 at 16:03

2 Answers2

1

I agree that C# complex object/dictionary initialization is not as simple as in other languages. I think you only need one tweak to make your attempt work, however:

private static Dictionary<string, List<object>> m_dCommandFuncs = new Dictionary<string, List<object>> {
    { "foo", new List<object>{1, 3, CommandFoo}},
    { "bar", new List<object>{2, 2, CommandBar}}
};
Tim
  • 2,089
  • 1
  • 12
  • 21
1

You can define a delegate type delegate int Command(string[] args); and initialize the dictionary like this:

    private static Dictionary<string, List<object>> m_dCommandFuncs = new Dictionary<string, List<object>>
    {
        { "foo", new List<object>{1, 3, (Command)CommandFoo}},
        { "bar", new List<object>{2, 2, (Command)CommandBar}}
    };

As others have said you have to explicitly write that you want new list. The cast is needed because the compiler complains that it's unable to convert method group to object. You can also cast to Func<string[],int>. To avoid using List<object> you should define new class like this:

    class CommandConfig
    {
        public int Lower { get; set; }
        public int Upper { get; set; }
        public Command Command { get; set; }
    }
    private static Dictionary<string, CommandConfig> m_dCommandFuncs2 = new Dictionary<string, CommandConfig>
    {
        {"foo", new CommandConfig {Lower = 1, Upper = 3, Command = CommandFoo}},
        {"bar", new CommandConfig {Lower = 2, Upper = 2, Command = CommandBar}}
    };

Command is still the delegate type.

mcbr
  • 731
  • 5
  • 6